Композиция.

20-01-2020

Возможность повторного использования кода принадлежит к числу важнейших преимуществ Java. При этом изменения не сводятся к копированию и правке кода. Существуют два способа повторного использования классов – композиция и наследование.

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

Во втором случае новый класс создается как специализация уже существующего класса. Взяв существующий класс за основу, к нему добавляется код без изменения существующего класса. Этот механизм называется наследованием, и большую часть работы в нем совершает компилятор. Наследование является одним из «краеугольных камней» объектно ориентированного программирования.

Между классами существуют разные типы отношений. Самым базовым типом отношений является ассоциация. Это означает, что два класса как-то связаны между собой, и мы пока не знаем точно, в чем эта связь выражена, и собираемся уточнить ее в будущем. Применительно к созданию классов на основе уже существующих (классов), в широком смысле, используется термин «композиция», т.е. класс создается на основе существующих классов. В то же время, в более узком смысле, при создании таких классов используются термины «композиция» и «агрегация». Ассоциация является общим случаем композиции и агрегации. Как композиция, так и агрегация обычно выражаются в том, что класс целого содержит свойства своих составных частей. Разница между композицией и агрегацией заключается в том, что в случае композиции целое явно контролирует время жизни своей составной части (часть не существует без целого), а в случае агрегации целое хоть и содержит свою составную часть, время их жизни не связано (например, составная часть передается через параметры конструктора).

Пример агрегации: Студент входит в Группу любителей физики.

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

UML-нотация: в UML-нотации композиция обозначается как линия со стрелкой в виде ромбика, указывающей на свои составляющие. Ромбик всегда находится со стороны целого, а простая линия со стороны составной части; закрашенный ромб означает более сильную связь – композицию, незакрашенный ромб показывает более слабую связь – агрегацию. Наиболее часто для описания отношений между классами используется ассоциация в форме композиции или наследование

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

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

В ООП классы часто организуют иерархически, чтобы избежать дублирования и уменьшить избыточность. Классы внизу иерархии наследуют все поля (статические атрибуты) и методы (динамическое поведение) из классов, находящихся выше по иерархии.

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

Подкласс наследует все переменные и методы из суперкласса, включая своего ближайшего родителя, так же как и всех остальных предков, за исключением переменных и методов с модификатором private. Важно заметить, что подкласс не является подмножеством суперкласса. Наоборот, подкласс является «супермножеством» суперкласса. Это происходит потому, что подкласс наследует все поля и методы суперкласса и, кроме того, расширяет суперкласс, предоставляя дополнительные поля и методы.

В Java подкласс определяется путем использования ключевого слова “extends”.

Обозначения UML: В обозначениях UML для наследования используется сплошная линия с незакрашенной стрелкой от подкласса к суперклассу. По соглашению, суперкласс изображается выше подкласса.

Области видимости

Область видимости – это область программы, в пределах которой идентификатор некоторой переменной, метода или класса является связанным с этой переменной, соответственно, методом или классом. За пределами области видимости тот же самый идентификатор может быть связан с другими переменными, методами, классами.

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

Переопределение методов и сокрытие полей

Подкласс наследует все переменные и методы (кроме переменных и методов с модификатором доступа private) из суперкласса (ближайшего родителя и всех предков). Подкласс может использовать унаследованные поля и методы в соответствии с тем, как они были определены. В подклассе можно также переопределить унаследованный метод, предоставив его собственную версию, или скрыть унаследованную переменную, определив переменную с тем же самым именем.

Аннотация @Override

@Override известно как аннотация (введено в JDK 1.5), которая запрашивает от компилятора проверку, существует ли такой метод в суперклассе для переопределения. Это хорошо помогает, если сделана ошибка в имени переопределяемого метода. Например, предположим, что мы хотим переопределить метод toString() в подклассе. Если @Override не используется и в имени toString() сделана ошибка, например, написано TOString(), то это будет рассматриваться как новый метод в подклассе вместо переопределения в суперклассе. Если @Override используется, то компилятор сообщит об ошибке. Аннотация @Override не обязательна, но стоит ее иметь. Аннотации не являются программными конструкциями. Они не влияют на результат работы программы. Используются они только на этапе компиляции, после компиляции уничтожаются и не используются при выполнении.

Ключевое слово “super”

Повторимся, что внутри определения класса можно использовать ключевое слово “this” для ссылки на данный экземпляр класса. Похожим образом, ключевое слово “super” отсылает к суперклассу, который может быть или ближайшим родителем или предком. Ключевое слово “super” позволяет подклассу получить доступ к полям и методам суперкласса из определения подкласса. Например, super() и super(список аргументов) может быть использован для вызова конструктора суперкласса. Если подкласс переопределяет метод, унаследованный от суперкласса, например, getArea(), то можно использовать super.getArea() для вызова версии суперкласса из определения подкласса. Похожим образом, если подкласс скрывает одну из переменных суперкласса, вы можете использовать super.имяПеременной для ссылки на скрытую переменную в определении подкласса.

Дополнение о конструкторах

Повторимся, что подкласс наследует все поля и методы суперкласса. Тем не менее подкласс не наследует конструкторов суперкласса. Каждый класс в Java определяет свои собственные конструкторы. В теле конструктора можно использовать super(args), чтобы вызвать конструктор своего ближайшего суперкласса. Обратите внимание, что super(args), если используется, должно быть первым предложением в конструкторе подкласса. Если super(args) не используется в конструкторе, Java-компилятор автоматически включает инструкцию super() для вызова конструктора без параметров своего ближайшего суперкласса. Это отражение того факта, что родитель должен быть рожден до того, как может родиться ребенок. Следует научиться правильно создавать конструкторы суперкласса до того, как конструировать подкласс.

Конструктор без параметров по умолчанию

Если в классе неопределен ни один конструктор, Java-компилятор создает конструктор без параметров, который просто запрашивает вызов super() следующим образом:

public ClassName(){
  super(); //вызов конструктора суперкласса без аргументов
}

Обратите внимание, что:

  • Конструктор по умолчанию без аргументов не будет автоматически сгенерирован, если хотя бы один (или более) конструкторов уже были определены. Другими словами, определять конструктор без аргументов при наследовании надо только в том случае, когда другие конструкторы отсутствуют.
  • Если ближайший суперкласс не имеет конструктора по умолчанию (т.е. определены несколько конструкторов, но не определен конструктор без параметров), то будет получена ошибка компиляции при выполнении вызова super(). Обратите внимание, что Java-компилятор вставляет super() как первое предложение в конструкторе, если нет super(args).

Одиночное наследование

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

Общий корневой класс java.lang.Object

Все классы Java являются наследниками общего корневого класса Object. Класс Object определяет и реализует общее поведение всех Java-объектов, выполняемых JRE. Такое общее поведение включает в себя, например, реализацию многопоточности и сборку мусора.

Литература

  1. О.И. Гуськова, "ООП в Java", Москва, 2018