Gerarchie di classi

Riusare classi | Inizializzare la classe base | Composizione di classi e ereditarietà | Composizione di classi vs ereditarietà

Riusare classi

Un modo per riutilizzare del codice già definito consiste nell'introdurre una nuova classe come un tipo di una classe esistente. Tramite il meccanismo dell'ereditarietà si aggiunge del codice che specializza la classe di partenza. Per definire una classe che ne specializzi una di base si deve usare la parola riservata extends nome-classe-base.

Un esempio

La classe base Disinfettante, viene estesa dalla classe Detergente:

class Disinfettante 
{
        private String s = new String ("disinfettante");
        
        
        public void appendi (String arg)
        {
                s += arg;
        }
        public void diluisci ()
        {
                appendi (" diluisci ()");
        }
        public void applica ()
        {
                appendi (" applica ()");
        }
        public void gratta ()
        {
                appendi (" gratta ()");
        }
        public void print ()
        {
                System.out.println (s);
        }
        
        public static void main (String [] args)
        {
                Disinfettante dis = new Disinfettante ();
                dis.diluisci();
                dis.applica();
                dis.gratta();
                dis.print();
        }
}
public class Detergente extends Disinfettante 
{
        // sovrascrive il metodo gratta ()
        public void gratta ()
        {
                appendi ( " Detergente.gratta()");
                super.gratta (); // chiamata al metodo della superclasse
        }
        
        // aggiunge nuovi metodi all'interfaccia
        public void schiuma ()
        {
                appendi (" schiuma()");
        }
        
        public static void main (String [] arg)
        {
                Detergente det = new Detergente ();
                det.diluisci ();
                det.applica();
                det.gratta();
                det.schiuma();
                det.print ();
                System.out.println ("Test della classe base:");
                Disinfettante.main (arg);
        }
}

In questo esempio si nota che entrambe le classi definiscono un metodo main. È possibile che ogni classe definisca un metodo main per facilitare il compito di verificare ciascuna delle classi durante la loro definizione o nei successivi cambiamenti. La cosa è possibile purché ci sia solo una classe dichiarata con accesso public: solo il metodo main di questa classe verrà automaticamente chiamato. Gli altri, come nell'esempio, potranno essere invocati nel codice direttamente. In Detergente.main() si invoca esplicitamente il metodo main() della superclasse, con l'istruzione Disinfettante.main(). Non è necessario eliminare i metodi main delle diverse classi, una volta effettuato il test; si possono lasciare per semplificare il compito in caso di ulteriori cambiamenti successivi.

Nella classe Detergente, i metodi appendi(), diluisci(), applica() vengono ereditati dalla superclasse Disinfettante e possono essere usati. L'ereditarietà può essere vista come un modo per riusare l'interfaccia. Il metodo gratta(), invece, viene ridefinito o sovrascritto: e per poter invocare il vecchio metodo della superclasse, occorre esplicitamente chiamarlo, usando il riferimento super, che si riferisce alla superclasse della classe corrente. Cioè super.gratta() chiama il metodo gratta() della classe base.

Possiamo poi aggiungere metodi all'interfaccia definendo metodi esclusivamente per la sottoclasse. In questo caso il metodo schiuma() sarà definito solo per la classe Detergente e non per la classe base Disinfettante.

Inizializzare la classe base

Mediante il meccanismo di ereditarietà si possono introdurre nuove classi da classi pre-esistenti. La nuova classe ha la stessa interfaccia della vecchia e probabilmente qualche nuovo metodo aggiuntivo. Un oggetto della nuova classe contiene quindi anche un oggetto della classe di base da cui deriva. Questo sotto-oggetto è come quello che avremmo creato nella classe base e ha vita nel contesto del nuovo oggetto.

Un esempio

La classe Schizzo
public class Schizzo extends Disegno 
{
	Schizzo ()
	{
		System.out.println ("costruisco un oggetto Schizzo");
	}
	
	public static void main (String [] arg)
	{
		Schizzo x = new Schizzo();
	}
}
specializza la classe Disegno,
class Disegno extends Arte 
{
	Disegno ()
	{
		System.out.println ("costruisco un oggetto Disegno");
	}
}
che a sua volta è una sottoclasse della classe Arte
class Arte 
{
	Arte ()
	{
		System.out.println ("costruisco un oggetto Arte");
	}
}

Il costruttore della classe Schizzo nell'istanziare un oggetto di tipo Schizzo richiama il costruttore della superclasse Disegno, che a sua volta richiama il costruttore della sua superclasse Arte. Infatti la chiamata al costruttore Schizzo() causa la stampa dei tre messaggi:

costruisco un oggetto Arte
costruisco un oggetto Disegno
costruisco un oggetto Schizzo

dove si noti l'ordine con cui sono stati istanziati gli oggetti a partire da quello più alto in gerarchia.
I costruttori della superclasse vengono chiamati automaticamente; questo non è però possibile nel caso in cui i costruttori richiedano parametri: in questo caso è necessario invocare esplicitamente il costruttore della superclasse e passare i parametri necessari con l'istruzione super(lista_parametri).

Combinare la composizione di classi e l'ereditarietà

È molto comune usare sia la combinazioni di classi sia l'ereditarietà per creare strutture di classi complesse.

Un esempio

class Piatto 
{
	Piatto (int i) 
	{
		System.out.println ("Costruttore di " +i+ " piatti");
	}
}

class Piatto_cena extends Piatto
{
	Piatto_cena (int i)
	{
		super (i);
		System.out.println ("Costruttore di " +i+ " piatti per posto");
	}
}

class Posata
{
	Posata(int i)
	{
		System.out.println ("Costruttore di " +i+ " posate");
	}
}

class Cucchiaio extends Posata
{
	Cucchiaio (int i)
	{
		super (i);
		System.out.println ("Costruttore di " +i+ " cucchiai");
	}
}

class Forchetta extends Posata
{
	Forchetta (int i)
	{
		super (i);
		System.out.println ("Costruttore di " +i+ " forchette");
	}
}

class Coltello extends Posata
{
	Coltello (int i)
	{
		super (i);
		System.out.println ("Costruttore di " +i+ " coltelli");
	}
}

class Posto_a_tavola
{
	Posto_a_tavola (int i)
	{
		System.out.println ("Costruttore di posti a tavola di base: " +i);
	}
}

public class Tavola extends Posto_a_tavola
{
	Cucchiaio cucch;
	Forchetta forch;
	Coltello colt;
	Piatto_cena piatti;
	
	Tavola(int i)
	{
		super (i+1);
		cucch = new Cucchiaio (i+1);
		forch = new Forchetta ((i+1)*2);
		colt = new Coltello (i+1);
		piatti = new Piatto_cena ((i+1)*3);
		System.out.println ("Costruttore di una tavola apparecchiata per "
				+ (i+1) + " posti");
	}
	
	public static void main (String [] arg)
	{
		Tavola x = new Tavola (2);
	}
}

La gerarchia tra le classi del programma è mostrata nella figura dove è messo in risulta sia la copmposizione tra le classi (la classe Tavola contiene sotto-oggetti delle classi Piatto_cena, Cucchiao, Forchetta e Coltello) sia la gerarchia determinata dalla ereditarietà.

grafico gerarchia

Il programma Tavola.java è tutto contenuto in un file, per questo è necessario che solo una classe sia dichiarata public: la classe Tavola che estende la classe Posto_a_tavola. La compilazione del programma produce tanti file bytecode quante sono le classi contenute nel singolo file e dà luogo al seguente output:

Costruttore di posti a tavola: 3
Costruttore di 3 posate
Costruttore di 3 cucchiai
Costruttore di 6 posate
Costruttore di 6 forchette
Costruttore di 3 posate
Costruttore di 3 coltelli
Costruttore di 9 piatti
Costruttore di 9 piatti per cena
Costruttore di una tavola apparecchiata per 3 posti

Il compilatore costringe a inizializzare la classe base e all'inizio del costruttore, ma non controlla che vengano inizializzati i membri della classe. Per questo è opportuno che le inizializzazione vengano effettuate nei costruttori.

Composizione di classi VS ereditarietà

Sia la composizione tra classi sia l'ereditarietà consentono di creare sotto-oggetti in una classe. La composizione viene di solito usata quando si vogliono le caratteristiche di una classe esistente ma non necessariamente la sua interfaccia. Il sotto-oggetto in questo caso viene immerso nel nuovo in modo da poterlo usare per implementare le nuove caratteristiche della nuova classe, ma l'utente vedrà solo l'interfaccia della nuova classe e non quella del sotto-oggetto immerso. Per questo effetto i sotto-oggetti sono definiti private nella nuova classe.

Altre volte è utile lasciare che l'utente della nuova classe possa direttamente accedere al sotto-oggetto: in questo caso essi vanno definiti public. Come nell'esempio Automobile.java, in cui i membri public dei sotto-oggetti possono essere utilizzati direttamente:

class Motore
{
	public void accendi () 
	{
	}
	
	public void indietro ()
	{
	}
	
	public void spegni ()
	{
	}
}

class Pneumatico
{
	public void gonfia ( int psi )
	{
	}
}

class Finestrino
{
	public void su ()
	{
	}
	
	public void giu ()
	{
	}
}

class Porta
{
	public Finestrino finestrino = new Finestrino();
	
	public void apri () 
	{
	}
	
	public void chiudi ()
	{
	}
}

public class Automobile 
{
	public Motore motore = new Motore();
	public Pneumatico [] pneumatici = new Pneumatico [4];
	public Porta porta_sn = new Porta ();
	public Porta porta_dx = new Porta ();
	
	Automobile ()
	{
		for (int i = 0; i < 4; i++)
			pneumatici[i] = new Pneumatico();
	}
	
	public static void main (String [] arg)
	{
		Automobile auto = new Automobile ();
		auto.pneumatici[0].gonfia(72);
		auto.porta_sn.chiudi();
		auto.motore.accendi();
		auto.porta_sn.finestrino.su ();
		auto.motore.indietro();
	}
}

Nell'ereditarietà invece si prende una classe esistente e la si specializza per uno scopo particolare. Dipende dal problema scegliere se specializzare le classi o comporle. Nel caso dell'esempio dell'automobile ha senso comporre le classi perchè un'automobile è composta da varie componenti. La composizione esprime la relazione has-a mentre l'ereditarietà, esprime la relazione is-a.