JavaRush /Java-Blog /Random-DE /Mehrfachvererbung in Java. Vergleich von Zusammensetzung ...
HonyaSaar
Level 26
Москва

Mehrfachvererbung in Java. Vergleich von Zusammensetzung und Vererbung

Veröffentlicht in der Gruppe Random-DE
Vor einiger Zeit habe ich mehrere Beiträge über Vererbung, Schnittstellen und Komposition in Java geschrieben. In diesem Artikel befassen wir uns mit der Mehrfachvererbung und lernen dann die Vorteile der Komposition gegenüber der Vererbung kennen.
Mehrfachvererbung in Java.  Vergleich von Zusammensetzung und Vererbung - 1

Mehrfachvererbung in Java

Unter Mehrfachvererbung versteht man die Möglichkeit, Klassen mit mehreren übergeordneten Klassen zu erstellen. Im Gegensatz zu anderen gängigen objektorientierten Sprachen wie C++ unterstützt Java keine Mehrfachklassenvererbung. Er unterstützt dies nicht, da die Wahrscheinlichkeit besteht, dass wir auf das „Diamantproblem“ stoßen, sondern bietet stattdessen lieber einen umfassenden Lösungsansatz an, bei dem wir die besten Optionen nutzen, um ein ähnliches Vererbungsergebnis zu erzielen.

„Das Diamantenproblem“

Um das Diamantproblem einfacher zu verstehen, nehmen wir an, dass Mehrfachvererbung in Java unterstützt wird. In diesem Fall können wir Klassen mit der in der folgenden Abbildung gezeigten Hierarchie erhalten. Diamant-KlassenhierarchieNehmen wir an, dass es sich um SuperClasseine abstrakte Klasse handelt, die eine bestimmte Methode beschreibt, und dass die Klassen ClassAund ClassBreale Klassen sind. 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(){
    }
}
Nehmen wir nun an, dass die Klasse gleichzeitig von und ClassCerbt und gleichzeitig über die folgende Implementierung verfügt: ClassA ClassB
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
    public void test(){
        //вызов метода родительского класса
        doSomething();
    }
}
Beachten Sie, dass die Methode eine Methode der übergeordneten Klasse test()aufruft , was zu Mehrdeutigkeiten führt, da der Compiler nicht weiß, welche Methode der Oberklasse aufgerufen werden soll. doSomething()Aufgrund der Form des Klassenvererbungsdiagramms in dieser Situation, die dem Umriss einer facettierten Raute ähnelt, wird das Problem als „Diamantenproblem“ bezeichnet. Dies ist der Hauptgrund, warum Java die Vererbung mehrerer Klassen nicht unterstützt. Beachten Sie, dass dieses Problem bei der Vererbung mehrerer Klassen auch bei drei Klassen auftreten kann, die mindestens eine gemeinsame Methode haben.

Mehrfachvererbung und Schnittstellen

Sie haben vielleicht bemerkt, dass ich immer sage: „Mehrfachvererbung wird zwischen Klassen nicht unterstützt“, aber zwischen Schnittstellen wird sie unterstützt. Ein einfaches Beispiel ist unten dargestellt: InterfaceA.java
package com.journaldev.inheritance;
public interface InterfaceA {

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

public interface InterfaceB {

    public void doSomething();
}
Beachten Sie, dass beide Schnittstellen eine Methode mit demselben Namen haben. Nehmen wir nun an, wir haben eine Schnittstelle, die von beiden Schnittstellen erbt. InterfaceC.java
package com.journaldev.inheritance;

public interface InterfaceC extends InterfaceA, InterfaceB {

    //метод, с тем же названием описан в  InterfaceA и InterfaceB
    public void doSomething();
Hier ist alles ideal, da Schnittstellen nur eine Reservierung/Beschreibung einer Methode sind und die Implementierung der Methode selbst in der konkreten Klasse erfolgt, die diese Schnittstellen implementiert, sodass bei der Mehrfachvererbung von Schnittstellen keine Möglichkeit besteht, auf Mehrdeutigkeiten zu stoßen. Aus diesem Grund können Klassen in Java von mehreren Schnittstellen erben. Lassen Sie es uns anhand des folgenden Beispiels zeigen. 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();
    }
}
Möglicherweise ist Ihnen aufgefallen, dass ich jedes Mal, wenn ich eine in einer Oberklasse oder in einer Schnittstelle beschriebene Methode überschreibe, die Annotation @Override verwende. Dies ist eine der drei integrierten Java-Annotationen und Sie sollten sie immer verwenden, wenn Sie Methoden überschreiben.

Komposition als Erlösung

methodA()Was ist also, wenn wir eine Klasse ClassAund eine methodB()Klassenfunktion verwenden möchten ? Eine Lösung hierfür könnte Composition sein – eine neu geschriebene Version , die beide Klassenmethoden implementiert und auch eine Implementierung für eines der Objekte hat. ClassB ClassС ClassC ClassA ClassB doSomething() 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();
    }
}

Zusammensetzung oder Vererbung?

Es ist eine gute Java-Programmierpraxis, die Komposition gegenüber der Vererbung zu nutzen. Wir werden einige Aspekte betrachten, die für diesen Ansatz sprechen.
  1. Angenommen, wir haben die folgende Kombination von Eltern-Erben-Klassen:

    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;
        }
    }

    Der obige Code lässt sich kompilieren und funktioniert einwandfrei, aber was wäre, wenn ClassCer anders implementiert würde:

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

    Beachten Sie, dass die Methode test()bereits in der Nachfolgeklasse vorhanden ist, aber ein Ergebnis eines anderen Typs zurückgibt. Falls Sie nun ClassDeine IDE verwenden, wird diese nicht kompiliert. Es wird Ihnen empfohlen, den Rückgabetyp in der Nachkommen- oder Oberklasse zu ändern.

    Stellen wir uns nun eine Situation vor, in der es eine mehrstufige Vererbung von Klassen gibt und die Oberklasse für unsere Änderungen nicht verfügbar ist. Um den Kompilierungsfehler zu beheben, bleibt uns nun keine andere Möglichkeit, als die Signatur oder den Namen der Unterklassenmethode zu ändern. Außerdem müssen wir an allen Stellen, an denen diese Methode aufgerufen wurde, Änderungen vornehmen. Daher macht die Vererbung unseren Code spröde.

    Das oben beschriebene Problem tritt bei der Zusammensetzung nie auf und macht diese daher gegenüber der Vererbung vorzuziehen.

  2. Das nächste Problem bei der Vererbung besteht darin, dass wir dem Client alle Methoden des übergeordneten Elements zugänglich machen. Und wenn die Superklasse nicht ganz korrekt konzipiert ist und Sicherheitslücken enthält. Auch wenn wir uns bei der Implementierung unserer Unterklasse voll um die Sicherheit kümmern, sind wir dennoch auf die fehlerhafte Implementierung der übergeordneten Klasse angewiesen.

    Die Komposition hilft uns dabei, kontrollierten Zugriff auf die Methoden einer Oberklasse bereitzustellen, während die Vererbung keine Kontrolle über deren Methoden behält. Dies ist auch einer der Hauptvorteile der Komposition gegenüber der Vererbung.

  3. Ein weiterer Vorteil der Komposition besteht darin, dass sie beim Aufrufen von Methoden mehr Flexibilität bietet. Die oben beschriebene Implementierung der Klasse ClassCist nicht optimal und verwendet eine frühe Bindung an die aufgerufene Methode. Durch minimale Änderungen können wir den Methodenaufruf flexibel gestalten und eine späte Bindung (Bindung zur Laufzeit) ermöglichen.

    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();
        }
    }

    Das obige Programm zeigt Folgendes an:

    doSomething implementation of A
    doSomething implementation of B

    Diese Flexibilität beim Methodenaufruf gibt es bei der Vererbung nicht, weshalb die Komposition der beste Ansatz ist.

  4. Unit-Tests sind im Fall der Komposition einfacher, weil wir wissen, dass wir für alle Methoden, die in der Superklasse verwendet werden, die Tests abbrechen können, während wir bei der Vererbung stark von der Superklasse abhängig sind und nicht wissen, wie die Methoden der übergeordneten Klasse funktionieren verwendet wird. Aufgrund der Vererbung müssen wir also alle Methoden der Oberklasse testen, was unnötige Arbeit bedeutet.

    Idealerweise sollte die Vererbung nur verwendet werden, wenn die „ Ist-ein “-Beziehung für die übergeordneten und untergeordneten Klassen zutrifft, andernfalls sollte die Zusammensetzung bevorzugt werden.

Originaler Artikel
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION