«Сегодня я узнал, что композиция строит отношения HAS-A, тогда как отношения наследования IS-A».

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

Наследование против композиции

Когда мы используем наследование, мы - неявно - говорим, что класс A является классом B.
Однако, когда мы используем композицию мы - неявно - говорим, что класс A имеет класс B.

Мы используем наследование, чтобы сгруппировать связанные концепции в иерархию, идентифицировать семейства классов и организовать имена и концепции, которые описывают предметную область.
Мы используем композицию, чтобы сделать «целое» из «частей».

Единственное правильное использование наследования - это когда класс A явно является подтипом класса B. В противном случае используйте композицию.

Эмпирическое правило здесь - спросить: «Является ли класс A классом B?». Если «да», то расширить (использовать наследование), иначе использовать композицию.

Правильный путь

Хватит разговоров, давайте посмотрим на код и увидим разницу между двумя реализациями.

Здесь мы используем наследование в порядке RecepCreditCard, чтобы повторно использовать код, написанный для CreditCard. Мы можем четко сказать, что «RecepCreditCard ЯВЛЯЕТСЯ кредитной картой». Это правильный способ использования наследования.

Взглянув на следующий пример кода,

Здесь мы хотели, чтобы RecepCreditCard также имел функцию «расчета кредитного рейтинга». Итак, мы использовали композицию, мы взяли части (класс CreditScoreCalculator), чтобы составить единое целое (класс RecepCreditCard с функцией расчета кредитного рейтинга). Здесь применяется наше определение состава: «Класс RecepCreditCard HAS-A CreditScoreCalculator».

Неправильный путь

После первого посещения правильного способа сделать это неправильное будет выглядеть настолько очевидным, но давайте все равно на это посмотрим. Мы хотим, чтобы наш класс RecepCreditCard имел функцию «Расчет кредитного рейтинга», и мы хотим, чтобы он имел свойства стандартной «кредитной карты». Первое, что приходит в голову, - расширить оба класса «CreditScoreCalculator» и «CreditCard», чтобы у нас были оба из них в нашей RecepCreditCard, верно? НЕПРАВИЛЬНЫЙ!

Java не позволяет расширять несколько классов. Единственный способ наследовать от нескольких классов - использовать интерфейсы. Но мы не будем вдаваться в подробности.

Итак, как это сделать по-другому? Мы можем подумать о расширении класса CreditScoreCalculator в классе CreditCard, а затем RecepCreditCard расширяет CreditCard, таким образом мы могли бы иметь все функции, которые хотели.

Теперь с этой реализацией есть несколько проблем.

Во-первых, мы расширили суперкласс CreditCard, чтобы сделать его в надлежащей форме для класса RecepCreditCard. Представьте себе другие классы кредитных карт, такие как «BankBankCreditCard» и «ThanksCreditCard», которые являются стандартными кредитными картами, но для них не требуется функция «Расчет кредитного рейтинга». После того, как мы расширили «CreditScoreCalculator» в «CreditCard», теперь для наших новых кредитных карт невозможно расширить с «CreditCard». Если мы все-таки расширимся, у нас останутся неиспользуемые методы в этих классах, что является индикатором плохого дизайна.

Вторая проблема здесь в том, что наш дизайн даже не соответствует нашим определениям. Мы говорим, что «наследование применяется, когда есть отношения IS-A». Здесь класс CreditCard ЯВЛЯЕТСЯ НЕ CreditCardCalculator, но мы расширили его.

Неправильное использование этой концепции также можно увидеть в «стеке» Java (java.util.Stack). В Java Stack расширяет класс Vector, но стек явно не является вектором, поэтому он может не только создавать проблемы понимания, но и приводить к ошибкам. Когда вы создаете объект класса Stack, предоставленный библиотекой Java, вы можете добавлять или удалять элементы из любого места в контейнере, потому что базовым классом является Vector, что позволяет вам удалять из любого места в векторе.

Вывод

Преимуществами использования состава являются:

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

Используйте наследование только в том случае, если расширяющийся класс действительно является расширением родителя. И иначе используйте композицию.

Ура!

использованная литература