JavaRush /Blog Java /Random-PL /Dziedziczenie wielokrotne w Javie. Skład a dziedziczenie
DSergey_Kh
Poziom 12

Dziedziczenie wielokrotne w Javie. Skład a dziedziczenie

Opublikowano w grupie Random-PL

Dziedziczenie wielokrotne w Javie

Dziedziczenie wielokrotne pozwala na utworzenie klasy, która dziedziczy z wielu nadklas. W przeciwieństwie do innych popularnych obiektowych języków programowania, takich jak C++, Java nie pozwala na wielokrotne dziedziczenie z klas. Java nie obsługuje dziedziczenia wielokrotnego klas, ponieważ może to prowadzić do problemu z diamentem. Zamiast szukać sposobów rozwiązania tego problemu, istnieją lepsze opcje, w jaki sposób możemy osiągnąć ten sam wynik, na przykład wielokrotne dziedziczenie.

Problem z diamentem

Aby łatwiej zrozumieć problem diamentu, załóżmy, że w Javie obsługiwane jest dziedziczenie wielokrotne. W tym przypadku moglibyśmy mieć hierarchię klas, jak pokazano na obrazku poniżej. Dziedziczenie wielokrotne w Javie.  Skład a dziedziczenie – 1Załóżmy, że klasa SuperClassjest abstrakcyjna i zadeklarowana jest w niej jakaś metoda. Zarówno klasy konkretne, ClassAjak i ClassB.
package com.journaldev.inheritance;
public abstract class SuperClass {
	public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of A");
	}
	//ClassA own method
	public void methodA(){
	}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
	@Override
	public void doSomething(){
		System.out.println("doSomething implementation of B");
	}
	//ClassB specific method
	public void methodB(){
	}
}
Załóżmy teraz, że chcemy ClassCgo zaimplementować i dziedziczyć z ClassAand ClassB.
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
	public void test(){
		//calling super class method
		doSomething();
	}
}
Należy zauważyć, że metoda test()wywołuje metodę nadklasy doSomething(). Prowadzi to do niejednoznaczności, ponieważ kompilator nie wie, którą metodę nadklasy wykonać. To jest diagram klas w kształcie rombu zwany problemem diamentu. Jest to główny powód, dla którego Java nie obsługuje wielokrotnego dziedziczenia. Należy zauważyć, że powyższy problem z dziedziczeniem wielokrotnym klas może wystąpić tylko w przypadku trzech klas, które mają co najmniej jedną wspólną metodę.

Dziedziczenie wielu interfejsów

W Javie dziedziczenie wielokrotne nie jest obsługiwane w klasach, ale jest obsługiwane w interfejsach. Jeden interfejs może rozszerzać wiele innych interfejsów. Poniżej znajduje się prosty przykład.
package com.journaldev.inheritance;
public interface InterfaceA {
	public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
	public void doSomething();
}
Należy zauważyć, że oba interfejsy deklarują tę samą metodę. Teraz możemy stworzyć interfejs rozszerzający oba te interfejsy, jak pokazano w poniższym przykładzie.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();
}
Działa to świetnie, ponieważ interfejsy deklarują jedynie metody, a implementacja zostanie wykonana w klasach, które dziedziczą interfejs. Dlatego nie ma możliwości uzyskania niejednoznaczności w przypadku dziedziczenia wielu interfejsów.
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
	@Override
	public void doSomething() {
		System.out.println("doSomething implementation of concrete class");
	}
	public static void main(String[] args) {
		InterfaceA objA = new InterfacesImpl();
		InterfaceB objB = new InterfacesImpl();
		InterfaceC objC = new InterfacesImpl();

		//all the method calls below are going to same concrete implementation
		objA.doSomething();
		objB.doSomething();
		objC.doSomething();
	}
}
Pamiętaj, że za każdym razem, gdy zastępujesz jakąkolwiek metodę nadklasy lub implementujesz metodę interfejsu, użyj adnotacji @Override. A co jeśli chcemy użyć funkcji methodA()klasy ClassAi funkcji methodB()klasy ClassBw klasie ClassC? Rozwiązanie polega na zastosowaniu kompozycji. Poniżej znajduje się wersja klasy ClassC, która wykorzystuje kompozycję do zdefiniowania zarówno metod klas, jak i metody doSomething()jednego z obiektów.
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();
	}
}

Skład a dziedziczenie

Jedną z najlepszych praktyk programowania w Javie jest „zatwierdzanie kompozycji przed dziedziczeniem”. Zbadamy niektóre aspekty, które przemawiają za tym podejściem.
  1. Załóżmy, że mamy nadklasę i klasę, która ją rozszerza:

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

    Powyższy kod kompiluje się i działa dobrze. Ale co, jeśli zmienimy implementację klasy, ClassCjak pokazano poniżej:

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

    Należy zauważyć, że metoda test()już istnieje w podklasie, ale typ zwracany jest inny. Teraz klasa ClassDnie będzie się kompilować, a jeśli użyjesz dowolnego IDE, wyświetli się monit o zmianę typu zwracanego w nadklasie lub podklasie.

    Wyobraźmy sobie teraz sytuację, w której mamy wielopoziomową hierarchię dziedziczenia klas i nie mamy dostępu do nadklasy. Nie będziemy mieli innego wyjścia, jak tylko zmienić sygnaturę metody naszej podklasy lub jej nazwę, aby usunąć błąd kompilacji. Będziemy musieli także zmienić metodę podklasy we wszystkich miejscach, w których jest ona wywoływana. Zatem dziedziczenie sprawia, że ​​nasz kod jest kruchy.

    Powyższy problem nigdy nie wystąpi w przypadku kompozycji, co czyni ją bardziej atrakcyjną dla dziedziczenia.

  2. Innym problemem związanym z dziedziczeniem jest to, że udostępniamy klientowi wszystkie metody nadklasy i jeśli nasza nadklasa nie jest odpowiednio zaprojektowana i występują luki w zabezpieczeniach, to nawet jeśli zaimplementujemy najlepszą implementację naszej klasy, wpływa na nas słaba implementacja z superklasy. Kompozycja pomaga nam w zapewnieniu kontrolowanego dostępu do metod nadklasy, podczas gdy dziedziczenie nie zapewnia kontroli nad metodami nadklasy. Jest to również jedna z głównych korzyści płynących z dziedziczenia kompozycji.

  3. Kolejną zaletą kompozycji jest to, że pozwala na elastyczność w wywoływaniu metod. Nasza implementacja klasy ClassCprzedstawionej powyżej nie jest optymalna i zapewnia, że ​​czas kompilacji jest powiązany z metodą, która zostanie wywołana. Przy minimalnych zmianach możemy uczynić wywołanie metody elastycznym i dynamicznym.

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

    Wynik programu przedstawionego powyżej:

    doSomething implementation of A
    doSomething implementation of B

    Ta elastyczność w wywoływaniu metod nie jest dostępna w przypadku dziedziczenia, co dodaje kolejną korzyść do wyboru kompozycji.

  4. Testowanie jednostkowe jest łatwiejsze do wykonania przy użyciu kompozycji, ponieważ wiemy, że używamy wszystkich metod z nadklasy i możemy je skopiować na potrzeby testu. Podczas gdy w dziedziczeniu bardziej polegamy na nadklasie i nie znamy wszystkich metod nadklasy, które zostaną użyte. Musimy więc przetestować wszystkie metody nadklasy, co jest dodatkową pracą ze względu na dziedziczenie.

    W idealnym przypadku powinniśmy używać dziedziczenia tylko wtedy, gdy relacja podklasy do nadklasy jest zdefiniowana jako „jest”. We wszystkich innych przypadkach zaleca się stosowanie kompozycji.

Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION