вторник, 16 июня 2020 г.

Объектно ориентированное программирование и принципы ООП


   Объектно-ориентированное программирование (ООП) - методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определённого класса, а классы образуют иерархию наследования.

    То есть все программы состоят из объектов, связанных между собой, вид и функционал объектов определяется классами.

   Такой подход к моделированию информационных объектов, позволяет решать таким способом основную задачу структурного программирования: структурирование информации с точки зрения управляемости.
   Это существенно улучшает управляемость самим процессом моделирования, что особенно важно при реализации крупных проектов.
   Управляемость для иерархических систем предполагает минимизацию избыточности данных (аналогичную нормализации) и их целостность, поэтому созданное удобно управляемым будет и удобно пониматься.
   Таким образом, через тактическую задачу управляемости решается стратегическая задача -  транслировать понимание задачи программистом в наиболее удобную для дальнейшего использования форму.

 
   Основные принципы структурирования ООП:
   (связаны с различными аспектами базового понимания предметной задачи, которое требуется для оптимального управления соответствующей моделью)

   1) абстракция данных
   «важное/неважное»

    Абстрагирование (отвлечение внимания от несущественного) - представление в виде абстракции - это выделение значимой информации (главных, наиболее важных характеристик объекта) и исключение из рассмотрения незначимой для описания информации.
   То есть приведение к использованию только необходимых параметров.
   В ООП рассматривают лишь абстракцию данных, для выделения в моделируемом предмете наиболее значимых характеристик объекта доступных остальной программе для решения конкретной задачи по предмету.
   В итоге - контекстное понимание предмета, формализуемое в виде класса.
   Основная цель использования данной концепции - уменьшение сложности компонентов программы за счет скрытия от программиста, использующего эти компоненты, ненужных ему подробностей 

    Уровень абстракции — это степень отвлеченности (условие, ситуация), подразумевающее, что на данном этапе какие-то свойства (признаки) объекта необходимо выделить, а какие-то проигнорировать.

    В зависимости от целей и задач, можно рассуждать об одном и том же объекте на разных уровнях абстракции.

    Программирование с помощью абстракции — это написание кода с использованием абстрактных классов и интерфейсов, когда во внимание не берется реализация деталей, а выделяются лишь какие-то общие признаки на основании которых выстраивается иерархия наследования (уровни абстракции), основанная на базовых свойствах родительского класса.

 



   2) инкапсуляция
   «ключевое/подробности»

   Инкапсуляция - это механизм языка, позволяющий ограничить прямой доступ к полям и методам класса извне (другим классам), с целью запретить бесконтрольную модификацию состояния объекта или вызов методов, которые также могут изменить его состояние.
   (сокрыть данные (поля) внутри объекта с целью защиты данных от внешнего, бесконтрольного изменения со стороны других объектов)

   Используется для быстрой и безопасной организации иерархической управляемости.
   Доступ к данным (полям) предоставляется посредством публичных методов (геттеров/сеттеров). Это защитный барьер позволяет хранить информацию в безопасности внутри объекта.
   Одни языки (например, С++, Java или Ruby) отождествляют инкапсуляцию с сокрытием, но другие (Smalltalk, Eiffel, OCaml) различают эти понятия.


   Прочитать больше об инкапсуляции, геттерах и сеттерах можно здесь: https://javaika.blogspot.com/2019/02/encapsulation-getters-and-setters.html


   3) наследование
   «родительское/дочернее»

    Наследование (inheritance) - свойство системы, позволяющее описать (создать) новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
    Класс, от которого производится наследование, называется базовым, родительским или суперклассом, а новый класс - потомком, наследником, дочерним или производным классом. 
    Используется для быстрой и безопасной организации родственных понятий: чтобы было достаточно на каждом иерархическом шаге учитывать только изменения, не дублируя всё остальное, учтённое на предыдущих шагах.

    Общий смысл наследования заключается в том, что если несколько классов имеют сходное поведение, то нет смысла дублировать их описание (писать с нуля), лучше выделить у них общие признаки и объединить их в общем для них родительском классе. При этом в описании самих классов оставить только различающиеся элементы (методы, поля) либо переопределить имеющиеся новой реализацией.

    Подробнее о наследовании здесь: https://javaika.blogspot.com/2022/10/nasledovanie.html


   4) полиморфизм подтипов
   «единое/множественное»

    Полиморфизм подтипов - свойство системы, позволяющее использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
    (Полиморфизм (многообразие форм) - это возможность объектов с одинаковой спецификацией иметь различную реализацию) 

   Данный принцип позволяет использовать одни и те же термины для описания различного поведения, зависящего от контекста (единообразно обрабатывать объекты с различной реализацией при условии наличия у них общего интерфейса или класса) (способность вызывать нужные методы у объектов, имеющие разные типы, но находящиеся в одной иерархии, при этом происходит автоматический выбор нужного метода в зависимости от типа объекта)
 
   Данный механизм называется — динамическим связыванием или поздним связыванием, или связыванием во время выполнения (а не во время компиляции).
 

Ограничения

  • Статические методы не поддерживают полиморфного поведения, т.к. они существуют на уровне класса, а не на уровне отдельных объектов

Достоинства

  • универсальность кода;

  • ускорение разработки, т.к. полиморфизм позволяет писать код, независящий от конкретных типов, находящихся в одной иерархии типов по отношению друг к другу. А это значит, что не нужно для каждого типа из этой иерархии писать дублирующий код;

  • снижение сложности программ, т.к. разрешая использование одного интерфейса для единого класса действий. При этом выбор конкретного действия, в зависимости от типа, возлагается на компилятор.

 

   Используется для определения точки, в которой единое управление лучше распараллелить или наоборот собрать воедино.
   Одной из форм полиморфизма в Java является переопределение метода, когда различные формы поведения определяются объектом из которого данный метод был вызван.
   В этом случае дочерний класс, используя концепцию полиморфизма, может изменить (переопределить) поведение метода родительского класса. Это позволяет программисту по разному использовать один и тот же метод, определяя поведение из контекста вызова (вызывается метод из класса предка или класса наследника).

   Другой формой полиморфизма является перегрузка метода, когда его поведение определяется набором передаваемых в метод аргументов.
 
   Перегрузка метода (method overloading) - механизм, позволяющий использовать методы с одним и тем же именем, но с разными типами и/или количеством параметров (несколько версий одного метода с разным набором входных параметров): при вызове метода, в зависимости от типа и количества передаваемых параметров, система выберет именно ту версию, которая наиболее подходит.
 
(Механизм перегрузки метода позволяет методу проявлять различное поведение в зависимости от того, какие аргументы он принимает.)

   Различие в типе возвращаемого значения для перегрузки не имеют никакого значения. 
 
   Перегрузка подпрограмм вводится для того чтобы иметь возможность использовать несколько вариантов подпрограммы с одним и тем же именем, но с разным числом аргументов или другими типами аргументов (то есть с разной сигнатурой, так как список аргументов - часть сигнатуры), вводится перегрузка подпрограмм.
   Перегружаемые функции имеют одинаковое имя, но разное количество или типы аргументов.
   Это разновидность статического полиморфизма, при которой вопрос о том, какую из функций вызвать, решается по списку её аргументов. Этот подход применяется в статически типизированных языках, которые проверяют типы аргументов при вызове функции.
   Перегруженная функция фактически представляет собой несколько разных функций, и выбор подходящей происходит на этапе компиляции.
   Перегрузку функций не следует путать с формами полиморфизма, где правильный метод выбирается во время выполнения, например, посредством виртуальных функций, а не статически. 
 
 
   Другой вид полиморфизма - параметрический - в ООП называют обобщённым программированием.
 

    Связывание (binding) — присоединение вызова метода к телу метода.

    Раннее связывание (early binding) — это связывание, происходящее во время компиляции. Также его называют статическим.

    При раннем связывании тип объекта уже должен быть известен. Но так бывает не всегда, если имеет место быть иерархия наследования или общий интерфейс.

     Например, если есть ссылка на базовый класс, например в качестве аргумента метода, а передается в него класс-потомок, а таких потомков может быть много. Тут компилятор сошел бы с ума, т.к не знал бы методы какого класса вызывать. Но для разрешения этой проблемы существовал другой вид связывания.

     Позднее связывание (late binding) — это связывание, которое выполняется во время выполнения программы. Его также называют динамическим (dynamic) или связыванием на стадии выполнения (runtime binding). При позднем связывании определяется фактический тип объекта для вызова именно его метода.


    Связывание всех методов в Java осуществляется полиморфно, через позднее связывание — это значит, что мы можем писать код для базового класса, который будет работать во всех производных классах от него.

 

    Восходящее преобразование (upcasting) — это когда, например метод в качестве параметра типа базового класса принимает тип класса наследника. Но это разумно, т.к. класс наследник является разновидностью класса предка. Данный факт позволяет преобразовывать ссылку на объект наследника в ссылку на объект предка.

Восходящим оно называется потому, что стрелки наследования идут снизу вверх, более того корень иерархии всегда вверху. Данное преобразование безопасно, т.к. это по сути переход от конкретного типа к более общему, что часто ведет к потере методов, но не приобретению.

Преобразования можно делать и в обратном порядке — это называется нисходящее преобразование (downcasting).

Типы отношений между классами

Прежде, чем дать определения отношениям между классами, хотелось бы внести некое разъяснение, которое поможет в понимании тонкостей данной терминологии. Речь пойдет о двух видах отношений — агрегации и композиции.

Если один объект состоит из других объектов (поля класса имеют ссылочный тип), которые создаются внутри этого объекта-контейнера и при этом время жизни "частей" зависит от времени жизни целого (умирает контейнер — погибают и его составляющий), то это называется композицией.

Но если после смерти объекта-контейнера его части остаются жить (не удаляются GC, тк на них ссылаются в другом месте программы), то это уже агрегация.

Если еще проще, то вся разница между агрегацией и композицией состоит в том, каким образом инициализируются поля в классе-контейнере и как эта инициализация влияет на время их жизни после смерти основного класса.

С точки зрения программирования - это выглядит так: если в классе-контейнере в теле конструктора (без передачи аргумента) или прямо в поле взять и присвоить ссылку на объект, то логично предположить, что со смертью основного класса умрут и все его внутренние объекты т.е. тут жесткая взаимозависимость — это композиция.

А, если создать объект, а потом передал его в качестве аргумента в сеттер или в конструктор, а потом присвоить его полю, то со смертью класса — все созданные таким образом объекты будут жить и дальше — это агрегация.

А теперь приведу примеры того, что пишут в интернете, возможно, кому-то они покажутся проще.

 
 

    Агрегация (aggregation; «has-a» — есть, имеет, содержит) применяется когда один класс должен быть контейнером для других классов. Причем время существования содержащихся в нем классов (полей) никак не зависит от времени существования класса контейнера.

    Агрегация — отношение «часть-целое» между двумя равноправными объектами, когда один объект (контейнер) имеет ссылку на другой объект. Оба объекта могут существовать независимо: если контейнер будет уничтожен, то его содержимое — нет.

Данный вид связи на UML-диаграмме обозначается в виде линии с незакрашенным ромбиком (ромбик всегда находится со стороны целого, а простая линия со стороны составной части).

 

    Композиция — еще более жесткое отношение, когда объект не только является частью другого объекта, но и вообще не может принадлежать еще кому-то. Когда объект уничтожается, объекты, составляющие его, также уничтожаются.

     Данный вид связи на UML-диаграмме обозначается в виде линии с закрашенным ромбиком (ромбик всегда находится со стороны целого, а простая линия со стороны составной части).

    Разница между агрегацией и композицией

разница между композицией и агрегацией заключается в том, что в случае композиции целое явно контролирует время жизни своей составной части (часть не существует без целого), а в случае агрегации целое хоть и содержит свою составную часть, время их жизни не связано.

различие между этими двумя видами ассоциации состоит в том, что композиция может быть частью одного и только одного целого, в то время как агрегация может быть частью нескольких объектов.

    Композицию часто предпочитают наследованию

один из хороших критериев выбора между композицией и наследованием - спросить себя, собираетесь ли вы впоследствии проводить восходящее преобразование от производного класса к базовому.

Достоинства

  • контроль видимости;
  • реализация может быть заменена во время выполнения (run-time);
  • слабая связанность, так как класс-интерфейс не зависит от реализации.
 
 
 
 
   Объект в программировании - сущность в цифровом пространстве, обладающая состоянием и поведением, имеющая поля и методы.
   Объекты (экземпляры класса) принадлежат одному или нескольким классам, которые определяют поведение (являются моделью) объекта.
 

  Экземпляр класса (англ. instance) - это описание конкретного объекта в памяти. Класс описывает поля и методы, которые будут доступны у объекта, построенного по описанию, заложенному в классе. Экземпляры используются для представления (моделирования) конкретных сущностей реального мира.

   Инстанцирование (англ. instantiation) — создание экземпляра класса. Создать экземпляр класса = инстанцировать класс. Порождающие шаблоны используют полиморфное инстанцирование.

   Анонимный объект (англ. anonymous object) — это объект, который принадлежит некоторому классу, но не имеет имени.

   Инициализация (англ. initialization) — присвоение начальных значений полям объекта.

   Время жизни объекта - время с момента создания объекта (конструкция) до его уничтожения (деструкция).
 
   Подробнее о классах здесь: https://javaika.blogspot.com/2020/11/java-class.html
 
 
   Объекты обладают свойствами наследования, инкапсуляции и полиморфизма.
   Объекты обладают такими характеристиками как состояние и поведение.
 
   Состояние объекта (state) — это значения, которые принимают его поля (fields). Со сменой значения полей — меняется состояние объекта.
   Поведение объекта (behavior) — это набор его методов (methods), которые могут изменять состояние объекта либо выполнять иные функции.
 






Больше об этом можно прочитать здесь: https://topjava.ru/blog/oops-concepts-in-java

Комментариев нет:

Отправить комментарий

Наследование в Java

   «родительское/дочернее»     Наследование (inheritance) - свойство системы, позволяющее описать (создать) новый класс на основе уже су...