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