3. Limitiamo l'ereditarietà
A volte è utile LIMITARE la possibilità di
derivare (e quindi modificare) il
comportamento di certi metodi o classi
Mantenendo il parallelo con la macchina del
caffè, dobbiamo essere CERTI che tutte
macchine usino llo stesso formato di
capsule, per esempio.
4. Come si fa?
Occorre precedere il nome di ciò che ci
interessa dalla parola chiave “final”
•
Una classe final è una classe che non può
essere estesa
•
Un metodo final è un metodo che non può
essere sovraccaricato nelle sottoclassi
•
Un attributo final è una variabile che può
essere assegnata una sola volta (simile a una
costante).
5. Esempio
public class Capsula {
public float Dimensioni() {...}
public final void Peso() {...}
}
public class MicroCapsula extends Capsula
{ … } → ERROR!
6. Estendiamo l'ereditarietà
• Nulla ci vieta, in teoria, di estendere il
processo di ereditarietà e fare discendere
il nostro oggetto da 2,3 o anche più
oggetti.
• Di fatto, aggiungiamo oggetti e metodi ”di
base” al nostro oggetto.
7. Troppo complicato!
Questo sistema è possibile in certi linguaggi
(es. il C++), anche se intruduce una serie
di problemi
Java usa un meccanismo leggermente
diverso, chiamato Interfaccia
8. Torniamo alla nostra macchina
Supponiamo ora di occuparci dell'alimentazione
della nostra macchina del caffè.
•
Una possibile soluzione consiste
nell'agganciaci alla normale rete di
alimentazione
•
Una seconda soluzione consiste nel mettere
delle batterie
•
Una terza possibile soluzione consiste
nell'usare accumulatori
9. In ogni caso..
Il sistema deve fornire le stesse funzionalità
base:
Un pulsante di accensione/spegnimento
Corrente continua a 12V.
12. Cos'è?
E' una “specie” di classe, ma
* Possiede solo metodi, non attributi (al
limite costanti statiche)
* Non ha implementazione, per cui non è
istanziabile.
13. Cos'è?
E' una sorta di “promessa”. Si garantisce
che si implementeranno quei metodi, un
po' come quando si aderisce ad uno
standard (.iso, .mp3).
14. Uso delle interfacce
public class Dicharazione
MacchinaCaffeCappBattIta di uso dell'
implements ElecticalAdapter {interfaccia
String PlugType(){return “Italian”;}
int InputVoltage(){return 220;};
}
Realizzazione
(anche gergo UML)
15. Perché darsi questa pena?
Le implementazioni possono variare anche
parecchio, ma il funzionamento osservabile è
identico.
Quindi la macchina funziona e fa il caffé. Può
essere alimentata a batterie, con la corrente o
con fluttuazioni nanotropiche del subspazio.
E i dettagli non ci interessano!
16. Altra implementazione
public class
MacchinaCaffeCappBattUs
implements ElecticalAdapter {
String PlugType(){return “Usa”;}
int InputVoltage(){return 110;};
}
17. Altra implementazione
public class
MacchinaCaffeCappBattBattery
implements ElecticalAdapter {
String PlugType(){return “”;}
int InputVoltage(){return 0;};
}
18. Anche le interfacce...
Nel loro piccolo, hanno delle gerarchie.
E' possibile estendere una interfaccia per
compiti più specializzati.
Sistema molto utilizzato (per esempio in
Swing e nelle Eccezioni)
19. Classi e metodi astratti
In certi casi si possono definire alcune classi o
metodi come “astratti” - analogamente alle
interfacce. Basta precederli con la parola chiave
'abstract'.
Anche in questo caso le tali classi non possono
essere istanziati.
Un solo metodo virtuale rende virtuale l'intera
classe
20. Classi e metodi astratti (2)
Di solito si preferisce l'uso delle interfacce, che
offrono più libertà di derivazione
Le classi astratte sono utilizzate spesso nella
parte iniziale di una progettazione OOD
21. Binding
In generale, nei linguaggi non OO, la
decisione su quale funzione viene
eseguita avviene sempre in fase di
compilazione.
Nel caso degli oggetti, invece, la decisione
può essere ritardata alla fase di
esecuzione (Late binding)
22. In pratica
Anche se Java è un linguaggio fortemente
tipizzato, è possibile assegnare ad una
variabile non solo un oggetto del tipo
previsto, ma anche uno dei suoi figli
Il messaggio che inviate all'oggetto è lo
stesso, ma l'effetto può variare.
23. Esempio
Supponiamo di avere un'applicazione che
gestisce tre macchine del caffé e può
controllarle tutte.
Creiamo un'array di macchine di caffé, ma
una di queste fa anche il cappuccino.
26. Ereditarietà.....
L'ereditarietà non è l'unico modo per estendere le
funzionalità del codice mantenendone l'integrità e
una elevata riusabilità.
Tipicamente, quando si creano sottoclassi si
estendono le funzionalità esistenti tramite l'override
Si usano classi/metodi astratti quando si vuole una
certa funzionalità ma non si sa bene come
implementarla
27. ...o aggregazione?
Un altro modo per creare funzionalità è
l'aggregazione (o la composizione).
In questo modo, si prendono altre classi e si
combinano in una nuova classe.
Si fornisce una nuova metodologia per
renderlo compatibile col codice esistente
28. Quale scegliere?
In molti casi, la scelta è obbligata e ovvia. In
altri, sono possibili entrambe le strade e
c'è un criterio preciso. Ci si basa
sull'esperienza.
Esempio: Da “punto” a “linea”. Da
“Macchina da caffe” a “Macchina da caffè
con batteria”
29. Riassumendo
Abbiamo così terminato questa breve introduzione
del mondo della programmazione OOP tramite
Java.
Spero abbia gettato un po' di luce su questa
“nuova” modalità di programmazione. (Per la
verità se ne parla dal 1960, e si usa
concretmente dagli anni novanta!)