Subtyping e Polimorfismo
Subtyping e Polimorfismo
Subtyping e Polimorfismo
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Subtype Polymorphism<br />
Conversioni di tipo<br />
• Variabile: locazione con un tipo associato<br />
Tipo della variabile determinato dal compilatore<br />
guardando la dichiarazione<br />
Una variabile di tipo reference contiene un riferimento<br />
ad un oggetto<br />
• Oggetto: istanza di una classe<br />
Tipo dell’oggetto: la classe che lo crea<br />
Determinato a run time<br />
• Una variabile può assumere come valori<br />
riferimenti ad oggetti di classi diverse<br />
Conversioni di tipo<br />
Continua…<br />
• La conversione è lecita solo in determinate<br />
situazioni<br />
Measurable x = new Rectangle(5, 10, 20, 30); // ERRORE<br />
• Problema: Rectangle non implementa<br />
Measurable<br />
• Interfacce e subtype polimorfismo<br />
Tipi, sottotipi e conversioni di tipo<br />
<strong>Polimorfismo</strong> e dinamic dispatch<br />
Conversioni di tipo<br />
• È possibile assegnare un riferimento di tipo<br />
classe ad una variabile di tipo interfaccia<br />
purchè la classe implementi l’interfaccia<br />
BankAccount account = new BankAccount(10000);<br />
Measurable x = account; // OK<br />
Coin dime = new Coin(0.1, "dime");<br />
Measurable y = dime; // OK<br />
<strong>Subtyping</strong><br />
Continua…<br />
• <strong>Subtyping</strong> (
<strong>Subtyping</strong><br />
• Principio di sostituibilità<br />
Un riferimento di un sottotipo può essere usato<br />
ovunque ci si aspetti un riferimento di un supertipo<br />
• Le regole di sottotipo devono garantire la<br />
correttezza del principio di sostituibilità<br />
<strong>Subtyping</strong> e regole di typing<br />
Continua…<br />
• Regola di assegnamento<br />
Un riferimento di tipo T1 si puo sempre assegnare ad una variabile<br />
di tipo T2 sse T1
<strong>Polimorfismo</strong> – dynamic dispatch<br />
• Possiamo invocare ognuno dei metodi<br />
dell’interfaccia:<br />
double m = x.getMeasure();<br />
• Quale metodo invoca?<br />
Domande<br />
5. È impossibile costruire un oggetto di tipo<br />
Measurable.Perché?<br />
6. Perché invece é possibile dichiarare una<br />
variabile di tipo Measurable?<br />
<strong>Polimorfismo</strong> – dynamic dispatch<br />
• Dipende dal riferimento corrente<br />
memorizzato nella variabile<br />
• Se x riferisce un BankAccount, invoca il<br />
metodo BankAccount.getMeasure()<br />
• Se x riferisce un Coin, invoca il metodo<br />
Coin.getMeasure()<br />
• <strong>Polimorfismo</strong> (molte forme):<br />
il comportamento varia, e dipende dal tipo dinamico<br />
della variabile<br />
Risposte<br />
5. Measurable è una interfaccia. Le<br />
interfacce non hanno campi o<br />
implementazione di metodo.<br />
Ancora un esempio Forme grafiche<br />
• Costruiamo una applicazione per disegnare<br />
un insieme di forme geometriche contenute<br />
in una componente grafico:<br />
definiamo GWin, una classe che descrive un<br />
contenitore di forme geometriche disegnate<br />
mediante una invocazione del metodo paint()<br />
per esemplificare, consideriamo due tipi di forme:<br />
Car e Smiley<br />
Continua…<br />
6. Perché Measurable è un tipo: la variabile<br />
non riferirà mai una istanza di Measurable,<br />
(le interfacce non hanno istanze) ma<br />
piuttosto oggetto di una qualche classe che<br />
implementa l’interfaccia Measurable.<br />
Continua…<br />
class Car<br />
{<br />
. . .<br />
public void draw(Graphics2D g)<br />
{<br />
// Istruzioni per il disegno<br />
. . .<br />
}<br />
} class Smiley<br />
{<br />
. . .<br />
public void draw(Graphics2D g)<br />
{<br />
// Istruzioni per il disegno<br />
. . .<br />
}<br />
}<br />
3
GWin<br />
• Un contenitore di Cars e Smileys<br />
/**<br />
Una finestra che contiene un insieme Cars e Smileys<br />
*/<br />
class GWin<br />
{<br />
/**<br />
Disegna tutte le forme di questo component<br />
*/<br />
public void paint(){ /* disegna su g */ }<br />
/**<br />
Componente grafica su cui disegnare<br />
*/<br />
private Graphics2D g;<br />
}<br />
Risposte<br />
• definiamo una nuova interfaccia: Shape<br />
• Ridefiniamo le classi Car e Smiley in modo<br />
che implementino Shape<br />
• Memorizziamo gli oggetti della componente<br />
in una ArrayList<br />
GWin<br />
interface Shape { void draw(Graphics2D g); }<br />
Mantiene una ArrayList<br />
class GWin<br />
{<br />
private Graphics2D g;<br />
private ArrayList shapes;<br />
// crea una GWin con un insieme di forme<br />
public GWin(Shape... shapes)<br />
{<br />
Graphics2D g = new Graphics2D();<br />
this.shapes = new ArrayList();<br />
for (Shape s:shapes) this.shapes.add(s);<br />
}<br />
// disegna tutte le componenti della GWin<br />
public void paint()<br />
{<br />
for (Shape s:shapes) s.draw(g);<br />
}<br />
}<br />
Domanda<br />
• Che struttura utilizziamo per memorizzare le<br />
forme contenute nella GWin?<br />
• Come definiamo il metodo paint() in modo<br />
che disegni tutte le forme della componente?<br />
Car e Smiley implementano Shape<br />
class Car implements Shape<br />
{<br />
}<br />
. . .<br />
public void draw(Graphics2D g)<br />
{<br />
// Istruzioni per il disegno<br />
. . .<br />
}<br />
class Smiley implements Shape<br />
Diagramma delle Classi<br />
GWin<br />
{<br />
}<br />
. . .<br />
public void draw(Graphics2D g)<br />
{<br />
// Istruzioni per il disegno<br />
. . .<br />
}<br />
Car<br />
Shape<br />
Smiley<br />
4
So long, for today<br />
Dynamic dispatch in GWin<br />
class GWin<br />
{<br />
. . .<br />
private ArrayList shapes;<br />
. . .<br />
public void paint()<br />
{<br />
// disegna tutte le componenti della GWin<br />
// il metodo invocato effettivamente da ogni<br />
// messaggio s.draw(g) dipende dalla classe<br />
// di cui s è istanza ed è deciso a runtime<br />
for (Shape s:shapes) s.draw(g);<br />
}<br />
. . . .<br />
}<br />
Dynamic dispatch vs overloading<br />
interface I<br />
{<br />
public String m(boolean b);<br />
public String m(double d);<br />
}<br />
class A implements I<br />
{<br />
public String m(boolean b) { return “A.m(boolean)”; }<br />
public String m(double d) { return “A.m(double)”; }<br />
}<br />
class B implements I<br />
{<br />
public String m(boolean b) { return “B.m(boolean)”; }<br />
public String m(double d) { return “B.m(double)”; }<br />
}<br />
<strong>Polimorfismo</strong> – dynamic dispatch<br />
• Dynamic dispatch:<br />
Il metodo da invocare per rispondere ad un<br />
messaggio è deciso a tempo di esecuzione<br />
Dynamic dispatch vs overloading<br />
• Dynamic dispatch:<br />
Il metodo da invocare per rispondere ad un<br />
messaggio è deciso a tempo di esecuzione<br />
• Notiamo bene<br />
Il metodo da invocare è deciso a runtime<br />
il compilatore decide se esiste un metodo da invocare<br />
• Overloading:<br />
Nel caso esista più di un metodo, il compilatore<br />
decide staticamente il tipo del metodo da invocare<br />
Dynamic dispatch vs overloading<br />
class Client<br />
{<br />
public void static show(I x)<br />
{<br />
// tipo del metodo invocato = m(boolean)<br />
// deciso dal compilatore staticamente<br />
// metodo invocato deciso dinamicamente<br />
// in funzione del tipo dell’argomento<br />
// passato per x<br />
System.out.println( x.m(false) );<br />
}<br />
}<br />
5
Domanda<br />
• Che cosa hanno in comune i meccanismi di<br />
overloading e di dynamic dispatch? In cosa<br />
sono diversi?<br />
<strong>Subtyping</strong> e sostituibilità<br />
• Principio di sostituibilità<br />
Un riferimento di un sottotipo può essere usato<br />
ovunque ci si aspetti un riferimento di un supertipo<br />
• Le regole di sottotipo garantiscono la<br />
correttezza del principio di sostituibilità<br />
Tutti i metodi del supertipo devono essere<br />
implementati dal sottotipo<br />
Il sottotipi può avere anche altri metodi<br />
Continua…<br />
Car e Smiley implementano Shape<br />
class Car implements Shape<br />
{<br />
}<br />
. . .<br />
public void draw(Graphics2D g){ . . . }<br />
public String brand() {. . . }<br />
class Smiley implements Shape<br />
{<br />
}<br />
. . .<br />
public void draw(Graphics2D g){ . . . }<br />
public String mood() {. . . }<br />
Risposta<br />
• Entrambi i meccanismi contribuiscono a<br />
decidono quale metodo eseguire in risposta<br />
ad un messaggio, ma<br />
• Nell’overloading scelta è relativa al tipo del metodo,<br />
ed è fatta in compilazione guardando il tipo dei<br />
parametri<br />
• Nel dynamic dispatch la scelta è relativa al corpo del<br />
metodo, ed è fatta in esecuzione guardando il tipo<br />
dell’oggetto che riceve il messaggio<br />
<strong>Subtyping</strong> e perdita di informazione<br />
• Principio di sostituibilità<br />
Un riferimento di un sottotipo può essere usato<br />
ovunque ci si aspetti un riferimento di un supertipo<br />
• Può causare perdita di informazione<br />
nel contesto in cui ci aspettiamo il supertipo, non<br />
possiamo usare solo I metodi del supertipo<br />
perdiamo la possibilità di utilizzare gli eventuali metodi<br />
aggiuntivi del sottotipo<br />
Continua…<br />
<strong>Subtyping</strong> e perdita di informazione<br />
• Consideriamo<br />
public static void printBrand(List l)<br />
{<br />
}<br />
for (Shape s : l)<br />
// stampa la marca di tutte le macchine di l<br />
// ???<br />
Continua…<br />
6
<strong>Subtyping</strong> e perdita di informazione<br />
• Certamente non possiamo fare così …<br />
public static void printBrand(List l)<br />
{<br />
}<br />
Cast<br />
for (Shape s : l)<br />
// stampa la marca di tutte le macchine di l<br />
System.out.println( s.brand() ); // TYPE ERROR!<br />
Continua…<br />
• Tipo statico e tipo dinamico di una variabile<br />
tipo statico: quello dichiarato<br />
tipo dinamico: il tipo del riferimento assegnato alla<br />
variabile<br />
• (T)var causa errore<br />
Cast<br />
in compilazione<br />
se T non è compatibile con il tipo statico di var<br />
in esecuzione (ClassCastException)<br />
se T non è compatibile con il tipo dinamico di var<br />
Shape s = new Car();<br />
• OK: Car sottotipo di Shape<br />
Smiley c = (Smiley) s<br />
• Compila correttamente<br />
il tipo dichiarato di s è Shape<br />
Smiley e Shape sono compatibili<br />
• Errore a run time<br />
s non è uno Smiley<br />
Continua…<br />
Continua…<br />
Cast<br />
• Permette di modificare il tipo associato ad una<br />
espressione<br />
((Car)s).brand()<br />
• Un cast è permesso dal compilatore solo se applica<br />
conversioni tra tipi compatibili<br />
• Compatibili = sottotipi (per il momento)<br />
• Anche quando permesso dal compilatore, un cast<br />
può causare errore a run time<br />
• Se s non è un Car errore a run time<br />
Cast<br />
Shape s = new Car();<br />
• OK: Car sottotipo di Shape<br />
Car c = (Car) s<br />
• Compila correttamente<br />
il tipo dichiarato di s è Shape<br />
Car e Shape sono compatibili<br />
• Esegue correttamente<br />
s è un Car (il tipo dinamico di s è Car)<br />
Cast<br />
• Attenzione anche qui …<br />
public static void printBrand(List l)<br />
{<br />
}<br />
for (Shape s : l)<br />
Continua…<br />
Continua…<br />
// ClassCastException se s instance of Smiley<br />
System.out.println( ((Car)s.)brand() );<br />
Continua…<br />
7
instanceof<br />
• Permette di determinare il tipo dinamico di una<br />
variabile<br />
x istanceof T è true solo se x ha tipo dinamico T<br />
• Quindi permette di evitare errori in esecuzione<br />
if (x instanceof T) return (T) x<br />
• Esegue correttamente, perchè x è sicuramente un T<br />
Domanda<br />
• E se volessimo disegnare solo le Shapes che<br />
sono Cars?<br />
Cast<br />
• Questo, finalmente, è corretto<br />
public static void printBrand(List l)<br />
{<br />
}<br />
Risposta<br />
for (Shape s : l)<br />
if (s instanceof Car)<br />
System.out.println( ((Car)s.)brand() );<br />
// disegna tutte le Cars della GWin<br />
public void paint(){<br />
for (Shape c:shapes)<br />
if (c instanceof Car) c.draw(g);<br />
}<br />
8