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. Nehmen wir an, dass es sich umSuperClass
eine abstrakte Klasse handelt, die eine bestimmte Methode beschreibt, und dass die Klassen
ClassA
und
ClassB
reale 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
ClassC
erbt 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
ClassA
und 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.-
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
ClassC
er 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 nunClassD
eine 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.
-
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.
-
Ein weiterer Vorteil der Komposition besteht darin, dass sie beim Aufrufen von Methoden mehr Flexibilität bietet. Die oben beschriebene Implementierung der Klasse
ClassC
ist 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.
-
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.
GO TO FULL VERSION