JavaRush /Java Blog /Random-IT /Ereditarietà multipla in Java. Composizione vs. ereditari...
DSergey_Kh
Livello 12

Ereditarietà multipla in Java. Composizione vs. ereditarietà

Pubblicato nel gruppo Random-IT

Eredità multipla in Java

L'ereditarietà multipla consente di creare una classe che eredita da più superclassi. A differenza di altri popolari linguaggi di programmazione orientati agli oggetti, come C++, Java non consente l'ereditarietà multipla dalle classi. Java non supporta l'ereditarietà di classi multiple perché può portare al problema del diamante. E invece di cercare modi per risolvere questo problema, ci sono opzioni migliori su come ottenere lo stesso risultato come l'ereditarietà multipla.

Problema dei diamanti

Per comprendere più facilmente il problema del diamante, supponiamo che Java sia supportata dall'ereditarietà multipla. In questo caso, potremmo avere una gerarchia di classi come mostrata nell'immagine qui sotto. Ereditarietà multipla in Java.  Composizione vs. ereditarietà - 1Supponiamo che la classe SuperClasssia astratta e che al suo interno sia dichiarato un metodo. Entrambe le classi concrete ClassAe 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(){
	}
}
Supponiamo ora di volerlo implementare ClassCed ereditare da ClassAe ClassB.
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
	public void test(){
		//calling super class method
		doSomething();
	}
}
Si noti che il metodo test()chiama il metodo della superclasse doSomething(). Ciò porta all'ambiguità perché il compilatore non sa quale metodo della superclasse eseguire. Questo è un diagramma di classi a forma di diamante chiamato problema del diamante. Questo è il motivo principale per cui Java non supporta l'ereditarietà multipla. Si noti che il problema precedente con l'ereditarietà di più classi può verificarsi solo con tre classi che hanno almeno un metodo comune.

Eredità di interfacce multiple

In Java l'ereditarietà multipla non è supportata nelle classi, ma è supportata nelle interfacce. E un'interfaccia può estendere molte altre interfacce. Di seguito è riportato un semplice esempio.
package com.journaldev.inheritance;
public interface InterfaceA {
	public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
	public void doSomething();
}
Tieni presente che entrambe le interfacce dichiarano lo stesso metodo. Ora possiamo creare un'interfaccia che estenda entrambe queste interfacce, come mostrato nell'esempio seguente.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
	//same method is declared in InterfaceA and InterfaceB both
	public void doSomething();
}
Funziona alla grande perché le interfacce dichiarano solo metodi e l'implementazione verrà eseguita in classi che ereditano l'interfaccia. Pertanto, non è possibile ottenere ambiguità nell'ereditarietà di più interfacce.
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();
	}
}
Tieni presente che ogni volta che esegui l'override di un metodo della superclasse o implementi un metodo di interfaccia, utilizza l'annotazione @Override. Cosa succede se vogliamo usare una funzione methodA()di una classe ClassAe una funzione methodB()di una classe ClassBin una classe ClassC? La soluzione sta nell'uso della composizione. Di seguito è riportata una versione della classe ClassCche utilizza la composizione per definire sia i metodi della classe che il metodo doSomething()di uno degli oggetti.
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();
	}
}

Composizione vs. ereditarietà

Una delle migliori pratiche di programmazione Java è "approvare la composizione prima dell'ereditarietà". Esploreremo alcuni degli aspetti che favoriscono questo approccio.
  1. Diciamo di avere una superclasse e una classe che la estende:

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

    Il codice sopra viene compilato e funziona bene. Ma cosa succede se cambiamo l'implementazione della classe ClassCcome mostrato di seguito:

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

    Tieni presente che il metodo test()esiste già nella sottoclasse, ma il tipo restituito è diverso. Ora la classe ClassDnon verrà compilata e se usi un IDE, ti verrà chiesto di modificare il tipo restituito nella superclasse o sottoclasse.

    Ora immagina una situazione in cui abbiamo una gerarchia di ereditarietà delle classi multilivello e non abbiamo accesso alla superclasse. Non avremo altra scelta che modificare la firma del metodo della sottoclasse o il suo nome per rimuovere l'errore di compilazione. Dovremo anche cambiare il metodo della sottoclasse in tutti i posti in cui viene chiamato. Pertanto, l’ereditarietà rende il nostro codice fragile.

    Il problema di cui sopra non si verificherà mai con la composizione e ciò la rende più attraente per l'ereditarietà.

  2. Un altro problema con l'ereditarietà è che esponiamo tutti i metodi della superclasse al client e se la nostra superclasse non è progettata correttamente e ci sono buchi di sicurezza, anche se implementiamo la migliore implementazione della nostra classe, siamo colpiti dalla scarsa implementazione della superclasse. La composizione ci aiuta a fornire un accesso controllato ai metodi della superclasse, mentre l'ereditarietà non fornisce il controllo sui metodi della superclasse. Questo è anche uno dei principali vantaggi della composizione per ereditarietà.

  3. Un altro vantaggio della composizione è che consente flessibilità nei metodi di chiamata. La nostra implementazione della classe ClassCpresentata sopra non è ottimale e garantisce che il tempo di compilazione sia legato al metodo che verrà chiamato. Con modifiche minime possiamo rendere la chiamata del metodo flessibile e dinamica.

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

    Il risultato del programma presentato sopra:

    doSomething implementation of A
    doSomething implementation of B

    Questa flessibilità nella chiamata del metodo non è disponibile con l'ereditarietà, il che aggiunge un altro vantaggio alla scelta della composizione.

  4. Il test unitario è più semplice da eseguire con la composizione perché sappiamo che stiamo utilizzando tutti i metodi della superclasse e possiamo copiarli per il test. Mentre nell'ereditarietà dipendiamo maggiormente dalla superclasse e non conosciamo tutti i metodi della superclasse che verranno utilizzati. Quindi dobbiamo testare tutti i metodi della superclasse, il che è un lavoro extra a causa dell'ereditarietà.

    Idealmente, dovremmo usare l'ereditarietà solo quando la relazione tra sottoclasse e superclasse è definita come "è". In tutti gli altri casi, si consiglia di utilizzare la composizione.

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