Buono sconto 4% su Toner e Cartucce agli utenti AZpoint. SU Iomiricarico.it!!
- Quando utilizzare l’ereditarietà
Quando si parla di ereditarietà, si è spesso convinti che per implementarla basti avere qualche classe che dichiari campi in comune. In realtà ciò potrebbe essere interpretato come un primo passo verso un’eventuale implementazione di ereditarietà. Il test decisivo deve però essere fatto mediante la cosiddetta "is a" relationship (la relazione "è un").
- La relazione "is a":
Per un corretto uso dell’ereditarietà, il programmatore dovrà porsi una fondamentale domanda: un oggetto della candidata sottoclasse "è un" oggetto della candidata superclasse? Se la risposta alla domanda è negativa, l’ereditarietà non si deve utilizzare. Effettivamente, se l’applicazione dell’ereditarietà, dipendesse solamente dai campi in comune tra due classi, potremmo trovare relazioni d’estensione tra classi quali:
class Triangolo
{
public final int numeroLati=3;
public float lunghezzaLatoUno;
public float lunghezzaLatoDue;
public float lunghezzaLatoTre;
. . . . . . . .
}
class Rettangolo extends Triangolo
{
public final int numeroLati=4;
public float lunghezzaLatoQuattro;
. . . . . . . .
}
ma ovviamente un rettangolo non è un triangolo e per la "is a" relationship questa estensione non è valida. La nostra esperienza ci dice che se iniziassimo un progetto software incrementale, senza utilizzare questo test, potremmo arrivare ad un punto dove la soluzione migliore per continuare sia quella di ricominciare da capo!
N.B.: siccome tutto è un oggetto, tutte le classi estenderanno Object.
- Generalizzazione e Specializzazione:
Sono due termini che definiscono i processi che portano all’implementazione dell’ereditarietà.
Si parla di generalizzazione, se a partire da un certo numero di classi, si definisce una superclasse che raccoglie le caratteristiche comuni. Viceversa si parla di specializzazione quando, partendo da una classe, si definiscono una o più sottoclassi, allo scopo di ottenere oggetti più specializzati.
Nell’ultimo esempio avevamo a disposizione la classe Triangolo e la classe Rettangolo. Abbiamo notato come il test "is a" fallendo ci "sconsigli" l’implementazione dell’ereditarietà. Eppure queste due classi hanno qualcosa in comune: si possono considerare entrambi poligoni. La soluzione quindi sembra consistere nel generalizzare in una classe che potremmo chiamare proprio Poligono, che potrebbe essere estesa dalle classi Triangolo e Rettangolo, nel modo seguente:
class Poligono
{
public int numeroLati;
public float lunghezzaLatoUno;
public float lunghezzaLatoDue;
public float lunghezzaLatoTre;
. . . . . . . .
}
class Triangolo extends Poligono
{
public final int numeroLati=3;
. . . . . . . .
}
class Rettangolo extends Poligono
{
public final int numeroLati=4;
public float lunghezzaLatoQuattro;
. . . . . . . .
}
Se fossimo partiti dalla classe Poligono per definire le due sottoclassi, avremmo parlato invece di specializzazione.
- Rapporto ereditarietà - incapsulamento:
Dal momento che l’incapsulamento si può considerare obbligatorio, e l’ereditarietà, un prezioso strumento di programmazione, bisognerà chiedersi cosa provocherà, l’utilizzo combinato di entrambi i paradigmi. Ovvero: cosa erediteremo da una classe incapsulata? Abbiamo già affermato che estendere una classe significa ereditarne i membri non privati. E’ quindi escluso che, in una nuova classe Ricorrenza ottenuta specializzando la classe Data definita precedentemente, si possa accedere alle variabili giorno, mese e anno, direttamente, giacché queste, non saranno ereditate. Ma, essendo tutti i metodi d’accesso alle variabili dichiarati pubblici nella superclasse, saranno ereditati e quindi utilizzabili nella sottoclasse. Concludendo, anche se la classe Ricorrenza non possiede esplicitamente le variabili private di Data, può comunque usufruirne tramite l’incapsulamento. In pratica le possiede virtualmente.
- Modificatore protected:
Oltre ai modificatori private e public, esiste un terzo specificatore d’accesso: protected. Un membro dichiarato protetto, sarà accessibile solo dalle classi appartenenti allo stesso package in cui è dichiarato, e può essere ereditato da sottoclassi appartenenti a package differenti. I package e tutti gli specificatori d’accesso saranno argomento del settimo capitolo.
- Conclusioni:
Le conclusioni che potremmo trarre dall’argomento ereditarietà sono ormai chiare ma non finali. Il concetto di ereditarietà ci ha aperto una strada con tante diramazioni, la strada della programmazione ad oggetti. Dalla definizione dell’ereditarietà nascono fondamentali concetti quali quelli del polimorfismo, nuovi potenti parole chiave come "super", e ci saranno nuove situazioni di programmazione da dover gestire correttamente. Dal prossimo modulo in poi, quindi, ci caleremo nel supporto che Java offre alla programmazione ad oggetti e nelle caratteristiche avanzate del linguaggi.
|