JavaRush /Blog Java /Random-FR /Héritage multiple en Java. Comparaison de la composition ...
HonyaSaar
Niveau 26
Москва

Héritage multiple en Java. Comparaison de la composition et de l'héritage

Publié dans le groupe Random-FR
Il y a quelque temps, j'ai écrit plusieurs articles sur l'héritage, les interfaces et la composition en Java. Dans cet article, nous examinerons l'héritage multiple, puis découvrirons les avantages de la composition par rapport à l'héritage.
Héritage multiple en Java.  Comparaison de composition et d'héritage - 1

Héritage multiple en Java

L'héritage multiple est la possibilité de créer des classes avec plusieurs classes parents. Contrairement à d'autres langages orientés objet populaires tels que C++, Java ne prend pas en charge l'héritage de classes multiples. Il ne le soutient pas en raison de la probabilité de rencontrer le « problème du diamant » et préfère plutôt proposer une sorte d’approche globale pour le résoudre, en utilisant les meilleures options permettant d’obtenir un résultat d’héritage similaire.

"Le problème du diamant"

Pour comprendre plus simplement le problème du diamant, supposons que l'héritage multiple soit pris en charge en Java. Dans ce cas, nous pouvons obtenir des classes avec la hiérarchie indiquée dans la figure ci-dessous. hiérarchie des classes de diamantsSupposons qu'il s'agisse SuperClassd'une classe abstraite qui décrit une certaine méthode, et ClassAque les classes ClassBsont de vraies classes. SuperClass.java
package com.journaldev.inheritance;
public abstract class SuperClass {
   	public abstract void doSomething();
}
ClassA.java
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
    @Override
 public void doSomething(){
        System.out.println("Какая-то реализация класса A");
    }
  //собственный метод класса  ClassA
    public void methodA(){
    }
}
Supposons maintenant que la classe ClassChérite de ClassAet ClassBen même temps, et ait en même temps l'implémentation suivante :
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
Notez que la méthode test()appelle une méthode doSomething()de la classe parent, ce qui entraînera une ambiguïté car le compilateur ne sait pas quelle méthode de superclasse doit être appelée. En raison de la forme du diagramme d’héritage de classes dans cette situation, qui ressemble au contour d’un diamant à facettes, le problème est appelé « problème du diamant ». C'est la principale raison pour laquelle Java ne prend pas en charge l'héritage de classes multiples. Notez que ce problème d’héritage de classes multiples peut également survenir avec trois classes ayant au moins une méthode commune.

Héritage et interfaces multiples

Vous avez peut-être remarqué que je dis toujours "l'héritage multiple n'est pas pris en charge entre les classes", mais il est pris en charge entre les interfaces. Un exemple simple est présenté ci-dessous : InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

    public void doSomething();
}
InterfaceB.java
package com.journaldev.inheritance;

public interface InterfaceB {

    public void doSomething();
}
Notez que les deux interfaces ont une méthode portant le même nom. Supposons maintenant que nous ayons une interface qui hérite des deux interfaces. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
Ici, tout est idéal, puisque les interfaces ne sont qu'une réservation/description d'une méthode, et que l'implémentation de la méthode elle-même se fera dans la classe concrète qui implémente ces interfaces, il n'y a donc aucune possibilité de rencontrer d'ambiguïté avec l'héritage multiple des interfaces. C'est pourquoi les classes Java peuvent hériter de plusieurs interfaces. Montrons-le avec l'exemple ci-dessous. InterfacesImpl.java
package com.journaldev.inheritance;

public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {

    @Override
    public void doSomething() {
        System.out.println("doSomething реализация реального класса ");
    }

    public static void main(String[] args) {
        InterfaceA objA = new InterfacesImpl();
        InterfaceB objB = new InterfacesImpl();
        InterfaceC objC = new InterfacesImpl();

        //все вызываемые ниже методы получат одинаковую реализацию конкретного класса

        objA.doSomething();
        objB.doSomething();
        objC.doSomething();
    }
}
Возможно, вы обратor внимание, что я каждый раз, когда я переопределяю метод описанный в суперклассе or в интерфейсе я использую аннотацию @Override. Это одна из трех встроенных Java-аннотаций и вам следует всегда использовать ее при переопределении методов.

Композиция How спасение

Так что же делать, если мы хотим использовать функцию methodA() класса ClassA и methodB() класса ClassB в ClassС? Решением этого может стать композиция – переписанная version ClassC реализующая оба метода классов ClassA и ClassB, также имеющая реализацию doSomething() для одного из an objectов. ClassC.java
package com.journaldev.inheritance;

public class ClassC{

    ClassA objA = new ClassA();
    ClassB objB = new ClassB();

    public void test(){
        objA.doSomething();
    }

    public void methodA(){
        objA.methodA();
    }

    public void methodB(){
        objB.methodB();
    }
}

Композиция or наследование?

Хорошим тоном программирования на Java является – использовать преимущества композиции перед наследованием. Мы рассмотрим некоторые аспекты в пользу такого подхода.
  1. Предположим, мы имеем следующую связку классов родитель-наследник:

    ClassC.java

    package com.journaldev.inheritance;
    
    public class ClassC{
    
    public void methodC(){
      	}
    
    }

    ClassD.java

    package com.journaldev.inheritance;
    
    public class ClassD extends ClassC{
    
        public int test(){
            return 0;
        }
    }

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

    package com.journaldev.inheritance;
    
    public class ClassC{
    
        public void methodC(){
        }
    
        public void test(){
        }
    }

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

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

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

  2. Следующая проблема с наследованием заключается в том, что мы демонстрируем все методы родителя клиенту. И если суперкласс разработан не очень корректно и содержит дыры в «безопасности». Тогда, несмотря на то, что мы fully позаботимся о безопасности при реализации нашего подкласса, мы все равно будем зависеть от ущербной реализации класса-родителя.

    Композиция помогает нам в обеспечении контролируемого доступа к методам суперкласса, тогда How наследование не поддерживает ниHowого контроля над его методами. Это - также одно из главных преимуществ композиции над наследованием.

  3. Еще одно преимущество композиции то, что оно добавляет гибкости при вызове методов. Реализация класса ClassC, описанная выше, не оптимальна и использует раннее связывание с вызываемым методом. Минимальные изменения позволят нам сделать вызов методов гибким и обеспечить позднее связывание (связывание во время выполнения).

    ClassC.java

    package com.journaldev.inheritance;
    public class ClassC{
        SuperClass obj = null;
        public ClassC(SuperClass o){
            this.obj = o;
        }
        public void test(){
            obj.doSomething();
        }
    
        public static void main(String args[]){
            ClassC obj1 = new ClassC(new ClassA());
            ClassC obj2 = new ClassC(new ClassB());
    
            obj1.test();
            obj2.test();
        }
    }

    Программа выше выведет на экран:

    doSomething implementation of A
    doSomething implementation of B

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

  4. Les tests unitaires sont plus faciles dans le cas de la composition car nous savons que pour toutes les méthodes utilisées dans la superclasse, nous pouvons écraser les tests, tandis qu'en héritage, nous dépendons fortement de la superclasse et ne connaissons pas le fonctionnement des méthodes de la classe parent. sera utilisé. Ainsi, à cause de l'héritage, nous devrons tester toutes les méthodes de la superclasse, ce qui représente un travail inutile.

    Idéalement, l'héritage ne devrait être utilisé que lorsque la relation « est-un » est vraie pour les classes parent et enfant, sinon la composition devrait être préférée.

Article original
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION