Множественное наследование в Java и Композиция vs Наследования

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

Множественное наследование в Java

Множественное наследование — возможность создания единого класса с несколькими суперклассами.

В отличие от некоторых других популярных объектно-ориентированных языках программирования, таких как C ++, Java не предоставляет поддержку множественного наследования в классах. Java не поддерживает множественное наследование классов, потому что это может привести к проблеме ромба (ромбовидное наследование) и вместо того, чтобы предоставлять сложный путь разрешения этой проблемы, придумали способ лучше.

Проблема ромба

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

multiinh

 

Давайте создадим абстрактный суперклас SuperClass с методом doSomething(), а также два класса ClassA, ClassB

SuperClass.java

ClassA.java

ClassB.java

А теперь давайте создадим класс ClassC, который наследует классы ClassA и ClassB

Обратите внимание, что метод test() вызывает метод суперкласса doSomething()

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

Множественное наследование в интерфейсах

Вы, возможно, заметили, я всегда говорю, что множественное наследование не поддерживается в классах, но оно поддерживается в интерфейсах и единый интерфейс может наследовать несколько интерфейсов, ниже простой пример.

InterfaceA.java

InterfaceB.java

Обратите внимание, что в обоих интерфейсах объявлен такой же метод, а теперь посмотрим, что с этого получится:

InterfaceC.java

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

Теперь давайте посмотрим на код ниже:

InterfacesImpl.java

А как здесь использовать Композицию (Composition)?

Так что же делать, если мы хотим использовать метод methodA() класса ClassA и метод methodB() класса ClassB  в ClassC? Решение заключается в использовании композиции. Ниже представлена версия класса ClassC с использованием композиции:

ClassC.java

Так что же использовать: Композицию или Наследование?

Одна из лучших практик программирования на Java гласит «Используйте композицию чаще наследования«. Давайте рассмотрим этот подход:

  • Предположим, у нас есть суперкласс и подкласс:

ClassC.java

ClassD.java

Код выше компилируется и работает нормально, но что будет, если реализация класса ClassC изменяется, как показано ниже:

Обратите внимание, что метод test() уже существует в подклассе, но тип возвращаемого отличается, теперь ClassD не будет компилироваться, и если вы используете какую-то IDE, то вам будет предложено изменить тип возвращаемого значения на тип суперкласса или подкласса.

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

Указанная проблема никогда не произойдет с композицией, поэтому это делает её более предпочтительней, чем наследование.

  • Еще одна проблема с наследованием в том, что мы предоставляем все методы суперкласса клиенту, и если наш суперкласс не правильно спроектирован и есть дыры в безопасности, то даже если мы позаботимся о правильной реализации нашего подкласса, мы все равно получаем проблемы, которые достались нам от суперкласса.
    Композиция помогает нам контролировать доступ к методам суперкласса, в то время как наследование не обеспечивает никакого контроля методов суперкласса. Это тоже одна из основных преимуществ композиции перед наследованием в Java.
  • Еще одно преимущество композиции в том, что она обеспечивает гибкость в вызове методов. Ниже приведен хороший пример использования композиции:

ClassC.java

Результат выполнения этой программы:

Эта гибкость в вызове методов не доступна в наследовании.

  • При использовании композиции легко проводить модульное тестирование, потому что мы знаем, что все методы не зависят от суперкласса. В то время как при наследовании, мы в значительной степени зависим от суперкласса и не знаем какие методы будут использоваться, поэтому мы должны проверить все методы суперкласса. А это дополнительная работа, которая никому не нужна.

В идеале мы должны использовать наследование только тогда, когда «is-a» отношение справедливо для суперкласса и подкласса во всех случаях, в противном случае мы должны использовать композицию.

Следите за обновлениями на javadevblog.com — программирование на Java

One thought to “Множественное наследование в Java и Композиция vs Наследования”

  1. В последнем примере в классе ClassC.java, скорее всего, используется агрегирование, а не композиция.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *