Polimorfismo File - e-Learning

Transcript

Polimorfismo File - e-Learning
In questa lezione
•  Polimorfismo (in Java)
–  Tipo statico e tipo dinamico dei reference
–  Binding dinamico (late binding)
–  Casting (Upcasting e Downcasting)
–  Operatore instanceOf
–  Polimorfismo con Object
Polimorfismo
•  I 3 meccanismi che caratterizzano un linguaggio
object-oriented
–  Incapsulamento (tipo di dato astratto, separazione fra
interfaccia e implementazione)
–  Ereditarietà (gerarchie di tipi e riuso)
–  Polimorfismo
1
Gerarchie di tipi e polimorfismo
•  Semantica IS_A
–  In una gerarchia di tipi, il
tipo più generale generale
(sovra-tipo) rappresenta
un’astrazione di tutti i tipi
che lo specializzano (sottotipi)
•  Il Polimorfismo si basa
sulla semantica IS_A
delle gerarchie di
generalizzazionespecializzazione definite
attraverso i meccanismi
di ereditarietà di Java
Tipo statico
Tipo dinamico
Poligono
p = new Rettangolo();
FiguraGeometrica f = new Ellisse();
FiguraGeometrica f = new Rettangolo();
Object
o = new Triangolo();
Poligono
p = new Ellisse();
Polimorfismo
•  Polimorfismo = (letteralmente) molteplici forme
•  In un programma
–  Proprietà del codice di comportarsi diversamente in diversi
contesti di esecuzione
•  Realizzazione nei linguaggi OO
–  Polimorfismo fra reference: usare una variabile reference per
riferirsi a oggetti di tipo (tipo dinamico) diverso da quello
dichiarato nel codice (tipo statico, noto a compile-time)
–  Polimorfismo per inclusione: La corrispondenza fra tipi statici e
dinamici è possibile nell’ambito le gerarchie di tipi definite con il
meccanismo di ereditarietà.
Ovvero, i possibili tipi dinamici per un reference sono solo i
sovra-tipi e sotto-tipi del tipo statico
2
Binding dinamico 1/2
(Vedi domanda nel codice sotto)
public class Poligono extends FiguraGeometrica {
public void disegna(){
…; //disegna un poligono
}
…
}
public class Rettangolo extends Poligono {
public void disegna(){
…; //disegna un rettangolo
}
…
}
public class Triangolo extends Poligono {
public void disegna(){
…; //disegna un triangolo
}
…
}
public class Diagramma {
public void inserisci(Poligono p){
…
//Domanda:
//Quale implementazione
//del metodo disegna?
p.disegna();
}
…
public static void main(String [] args){
//Corretto per polimorfismo:
Poligono p1 = new Rettangolo();
//Corretto per polimorfismo:
Poligono p2 = new Triangolo();
Diagramma d = new Diagramma();
d.inserisci(p1);
d.inserisci(p2);
}
}
Binding dinamico 2/2
•  Anche detto: late binding
•  Il legame fra la definizione e l’invocazione dei metodi
non viene stabilito “staticamente” dal compilatore
(compile-time), ma bensì “dinamicamente” a runtime
–  Il compilatore non ha modo di stabilire quale implementazione
del metodo disegna() deve chiamare: quella di Rettangolo, o
quella di Triangolo, o quella di Poligono?
–  Il tipo effettivo del parametro p è noto solo quando il metodo
“disegna” viene invocato, cioè solo a runtime
–  Tecnicamente: il compilatore non genera il codice per eseguire il
metodo, ma genera codice che riconosce il tipo effettivo di “p”,
cerca l’implementazione giusta di “disegna”, e la esegue
(dispatching)
•  In Java, il binding è sempre dinamico!!
•  NB: Il binding dinamico non è automatico in tutti i
linguaggi di programmazione. Ad esempio in C++ viene
abilitato inserendo la keyword “virtual” nella signature dei
metodi
3
Ammissibilità compile-time del
codice che usa il polimorfismo
•  Nei linguaggi “tipizzati” il compilatore verifica
l’ammissibilità del codice in base ai tipi dichiarati
(tipo statico)
–  Ad esempio: le invocazioni di metodo ammissibili sono solo
quelle ammissibili per il tipo statico dei reference
•  Perché il polimorfismo sia possibile, il compilatore
permette di assegnare fra loro reference di tipo diversi
–  Casting = coercizione di tipo.
Si impone al compilatore di considerare un dato (es. un
reference) con tipo diverso da quello dichiarato
•  In Java il casting è ammissibile in due forme:
–  Up-casting (polimorfismo type-safe)
–  Down-casting (polimorfismo type-unsafe)
Upcasting 1/2
(Quale programma è corretto?)
public class Diagramma {
public void inserisci(Poligono p){
p.disegna();
}
}
public static void main(String [] args){
Poligono p1 = new Rettangolo();
Poligono p2 = new Triangolo;
Diagramma d = new Diagramma();
d.inserisci(p1);
d.inserisci(p2);
}
public static void main(String [] args){
Rettangolo r = new Rettangolo();
Triangolo t = new Triangolo;
Diagramma d = new Diagramma();
d.inserisci(r);
d.inserisci(t);
}
Entrambi i programmi sono validi per il compilatore e producono il medesimo risultato!!!
4
Upcasting 2/2
•  Casting = coercizione di tipo
–  Si impone al compilatore di considerare un dato (es. un reference) con
tipo diverso da quello dichiarato
•  Upcasting: coercizione di un tipo specializzato verso un tipo più
generale (up = più in alto nella gerarchia di ereditarietà)
•  In Java (e in generale nei linguaggi con polimorfismo) l’upcasting
viene garantito implicitamente dal compilatore.
•  Per esempio (vedi programma slide precedente):
–  Poligono p1 = new Rettangolo(); //Upcasting del risultato della chiamata new,
che restituisce un reference di tipo Rettangolo, per l’assegnamento a una
variabile di tipo Poligono
–  d.inserisci(r); //Upcasting del reference r di tipo rettangolo per il passaggio del
parametro al metodo inserisci, che dichiara un parametro di tipo Poligono
• 
• 
NB: dopo un upcasting, si possono invocare solo i metodi presenti nel tipo
statico (si ricordi però il binding dinamico!!)
NB: Upcasting è type-safe: ovvero i metodi invocabili per il tipo statico
esistono sicuramente per qualsiasi tipo dinamico ammissibile
Problemi con up-casting
public class Poligono extends
FiguraGeometrica{
public void disegna(){
…; //disegna un poligono
}
…
}
public class Rettangolo extends Poligono {
public void disegna(){
…; //disegna un rettangolo
}
public double getBase(){
…; //restituisce la base
}
public double getAltezza(){
…; //restituisce l’altezza
}
…
}
public class Diagramma {
public void inserisci(Poligono p){
…
p.disegna();
}
…
public static void main(String [] args){
Poligono p1 = new Rettangolo();
Diagramma d = new Diagramma();
d.inserisci(p1);
double b = p1.getBase();
double a = p1.getAltezza();
System.out.println(“Area = ” + b*a);
}
}
Il compilatore segnalerebbe un errore. Qual è il problema?
Non è possibile invocare getBase() e getAltezza() per un ref di tipo Poligono (p1)
Messaggio di errore in compilazione:
“the method getBase() is undefined for the type Poligono”
5
Downcasting (type-unsafe)
• 
• 
• 
Downcasting: coercizione di un tipo generale verso un tipo più specializzato
(down = più in basso nella gerarchia di ereditarietà)
In Java il downcasting è valido,
ma deve essere dichiarato esplicitamente dal programmatore.
NB: il compilatore si fida!
public static void main(String [] args){
Poligono p1 = new Rettangolo();
Diagramma d = new Diagramma();
d.inserisci(p1);
double b = ((Rettangolo) p1).getBase(); //Downcasting di p1 prima di getBase
double a = ((Rettangolo) p1).getAltezza(); //Downcasting di p1 prima di getAltezza
System.out.println(“Area = ” + b*a);
}
• 
• 
È responsabilità del programmatore usare il downcasting solo quando ha senso!
Se la fiducia del compilatore è mal riposta (ovvero il programmatore effettua un
downcasting sbagliato), potranno verificarsi errori a runtime!
Errori runtime con Downcasting
•  Downcasting è type-unsafe: in un programma che passa
la compilazione, possono esserci errori nell’uso dei tipi!
•  Il seguente programma viene compilato correttamente
ma fallisce a runtime (java.lang.ClassCastException)
public static void main(String [] args){
Poligono p1 = new Esagono();
Diagramma d = new Diagramma();
d.inserisci(p1);
double b = ((Rettangolo) p1).getBase();
double a = ((Rettangolo) p1).getAltezza();
System.out.println(“Area = ” + b*a);
}
6
Altri casting
•  In Java, il casting di oggetti non primitivi è
legale solo per classi legate in una
gerarchia di ereditarietà
•  …ovvero solo Upcasting e Downcasting
sono legali
•  Un tentativo di effettuare un cast illegale
viene segnalato come errore a compiletime
Operatore instanceof
•  Permette di testare il tipo dinamico dell’oggetto
associato a un reference
•  Restituisce vero se il tipo del reference è
compatibile con il tipo specificato
void gestisciPoligono(Poligono p){
if(p istanceof Rettangolo)
((Rettangolo) p).getBase();
else…
}
7
Uso di instanceof: Qual’è l’output?
s = null;
if(s instanceof java.lang.String)
System.out.println(”String");
else System.out.println(”not Str");
class Parent {}
class Child extends Parent {}
public class Prova{
public static void main(String[]
args){
String s = "Hello";
if(s instanceof java.lang.String)
System.out.println(”String”);
else
System.out.println(”not Str");
}
Child child = new Child();
if (child instanceof Child)
System.out.println(”Child");
if (child instanceof Parent)
System.out.println(”Parent");
Parent parent = new Parent();
if (parent instanceof Child)
System.out.println(”Child");
else System.out.println(”Parent");
}}
Polimorfismo con Object 1/3
•  In Java tutte le classi sono in relazione di
ereditarietà con il tipo Object
•  Ne consegue che:
–  Upcasting (implicito) di un qualsiasi reference a
Object è sempre possibile
•  Dopo un upcasting a Object è possibile invocare solo i
metodi definiti dalla classe Object (per esempio equals o
toString)
•  …ma ricordate il binding dinamico!!
–  Downcasting (esplicito) di un reference di tipo Object
a una qualsiasi classe è sempre accettato dal
compilatore
•  anche se ovviamente può produrre errori runtime nei casi in
cui il tipo dinamico dell’oggetto non fosse compatibile con la
classe del casting
8
Polimorfismo con Object 2/3
public class Rettangolo extends
public class Main{
Poligono {
static void tracciaOggetto(Object o){
public String toString(){
String s = o.toString();
return “Un rettangolo”;
System.out.println(s);
}
…
public double getBase(){
}
…; //restituisce la base
public static void main(String [] args){
}
Rettangolo r = new Rettangolo(…);
}
Colore c = new Giallo(…);
public class Giallo extends Colore {
tracciaOggetto(r);
public String toString(){
tracciaOggetto(c);
return “Colore giallo”;
}
}
}
}
Il programma è valido!
Domanda: cosa viene stampato a video quando si esegue questo programma?
Polimorfismo con Object 3/3
public class Rettangolo extends
public class Prova{
Poligono {
public void tracciaOggetto(Object o){
public String toString(){
String s = o.toString());
return “Un rettangolo”;
System.out.println(s);
}
…
}
double b =
public double getBase(){
((Rettangolo)o).getBase();
…; //restituisce la base
}
}
public static void main(String [] args){
public class Giallo extends Colore {
Rettangolo r = new Rettangolo(…);
public String toString(){
Colore c = new Giallo(…);
return “Colore giallo”;
tracciaOggetto(r);
}
tracciaOggetto(c);
}
}
}
Il programma viene compilato correttamente, ma provoca un errore a runtime!
Domanda: quale output si vedrà in fase di esecuzione del programma?
9
Il modo giusto di implementare
equals
•  equals viene sempre ereditato dalla classe Object con
signature:
public boolean equals(Object otherObject){ … }
•  Questo metodo va generalmente ridefinito (overridden)
•  …ma attenzione a non definire impropriamente un
overload piuttosto che un override del metodo
public class Persona {
public boolean equals(Persona p){}
…
public class Persona {
public boolean equals(Object o){}
…
Overloading di equals
Overriding di equals
Il modo giusto di implementare
equals
•  La ridefinizione di equals deve garantire che
–  Il parametro passato non sia null
–  Il parametro passato non sia di tipo diverso
dall’oggetto con cui deve essere confrontato
–  Impementazione tipica:
public boolean equals(Object o){
if(this == o) return true;
if(o == null)return false;
if(getClass() != o.getClass()) return false;
Persona p = (Persona)o;
… //confronto fra this e p
}
10
Il metodo getClass()
•  Metodo della classe Object
–  È un metodo final, non può essere ridefinito
•  Restituisce una rappresentazione del tipo
dinamico dell’oggetto
–  Il risultato può essere confrontato con == o !=
per determinare se due oggetti sono stati creati
esattamente della stessa classe
if(object1.getClass() == object2.getClass()…
Nessun binding dinamico per…
•  Metodi dichiarati static
–  tali sono associati alle classi (e non agli oggetti)
infatti non si invocano attraverso un reference, ma
attraverso il nome della classe corrispondente
–  il nome di una classe rappresenta un unico tipo (non
c’è differenza fra tipo statico e dinamico), quindi non è
soggetto a polimorfismo
11
Nessun binding dinamico per…
•  Metodi dichiarati final
–  tali metodi non possono essere ridefiniti nelle
sottoclassi, quindi il binding dinamico non è
necessario
un metodo dichiarato final non può essere ridefinito (overridden) in
una classe derivata:
public class Triangolo {
public final void metodo(){ …}
}
public class TriangoloEquilatero extends Triangolo {
public void metodo(){ …} //non valido
ESERCIZI
12
Polimorfismo: esercizio 1
• 
• 
• 
• 
• 
• 
• 
Si consideri il sistema informativo di un negozio di alimentari…
Realizzare una classe Prodotto e una gerarchia di classi che estendono prodotto:
Biscotti, Verdura, ...
Ogni prodotto ha un prezzo che deriva dalla somma di costo fisso (che può differire
di prodotto in prodotto) e un prezzo variabile in base a caratteristiche specifiche di
ogni prodotto. Per i biscotti il prezzo specifico è 10 o 20 secondo che si tratti o meno
della “confezione grande”; Per la verdura il prezzo specifico dipende dal peso ed è di
5 per Kilo. Il prezzo dei prodotti è accessibile attraverso il metodo getPrezzo. La
descrizione di un prodotto è una stringa accessibile con il metodo toString e ne
include il prezzo
Si realizzi la classe Ordine che memorizza il totale (int) di un’ordinazione, man mano
che si aggiungono dei prodotti attraverso il metodo aggiungiProdotto e memorizza
la descrizione dell’ordine facendo uso di un oggetto di classe Descrizione
Un oggetto di tipo Descrizione incapsula una stringa ottenuta per concatenazioni
successive delle rappresentazioni testuali degli oggetti passati al metodo append
(che può ricevere oggetti di tipo qualsiasi).
Si realizzi un caso di test che crea alcuni prodotti, li aggiunge a un ordine, stampa la
descrizione dell’ordine e verifica la correttezza del prezzo
Si rifletta su quali punti del codice corrispondono a: invocazioni polimorfe con binding
dinamico, upcasting o downcasting
Polimorfismo: esercizio 2
(variabile di stato polimorfa)
•  Si realizzi la classe Confezione secondo la seguente specifica:
•  Ogni oggetto di tipo Confezione incapsula un altro oggetto che
rappresenta il contenuto della confezione. Il contenuto deve poter
essere un oggetto di tipo qualsiasi.
•  Il metodo scarta restituisce l’oggetto contenuto nella confezione e
stampa una stringa che descrive tale oggetto
•  Si realizzi un caso di test che inserisce in una confezione alcuni
oggetti Prodotto (vedi esercizio precedente) e verifica il
funzionamento del metodo “scarta”
•  Domanda: si ipotizzi una gerarchia “class Caramelle extends
Prodotto…” in cui il metodo toString definito nella classe Caramelle
fa override del metodo toString definito dalla classe Prodotto. Come
si fa a fare in modo che il metodo “scarta” invochi il toString di
Prodotto (superclasse) quando il contenuto della confezione è un
oggetto Caramelle (sottoclasse)? In particolare, si ragioni sulla
possibilità (o meno) di usare upcasting esplicito a questo scopo?
13
Polimorfismo: esercizio 3
(downcast, instanceof)
•  Si implementi una classe Cliente con un metodo “acquisisci” che
riceve come parametro un oggetto di tipo Confezione. Sotto l’ipotesi
che l’oggetto contenuto sia di classe Prodotto, ne restituisce il
prezzo, altrimenti restituisce 0. Inoltre se l’oggetto Prodotto è di tipo
Caramelle, stampa a video il numero di caramelle nella confezione
(si ipotizzi l’esistenza della classe Caramelle e del metodo “conta”
che restituisce il numero di caramelle)
•  Si realizzi un caso di test che invoca “acquisisci” passando prima
una Confezione che contiene un oggetto di classe Biscotti e poi una
Confezione che contiene un oggetto di classe Caramelle
•  Si realizzi un caso di test che invoca “acquisisci” passando una
Confezione che contiene un oggetto di classe Descrizione
•  Si simulino sul codice ottenuto alcuni casi in cui il downcast e
l’operatore instanceof falliscono staticamente (errore a compile time)
Polimorfismo: esercizio 4
•  Si implementi una classe File che incapsula un array di byte. Il
metodo getSize restituisce la dimensione (il numero di byte)
dell’oggetto File, e il metodo isBiggerThan restituisce true o false
secondo che l’oggetto File sia o non sia più grande di una data
dimensione
•  Si implementi una classe Folder che estende File e incapsula a sua
volta un array di File (max 10). Il metodo aggiungiFile permette di
inserire un oggetto File nel folder fino al raggiungimento del numero
massimo. Folder ridefinisce getSize come somma delle dimensioni
dei file contenuti nel Folder
•  Domande: in Folder è necessario ridefinire il metodo isBiggerThan?
Come si rappresenzano progettualmente (in UML) le relazioni fra i
File e i Folder secondo quanto definito nell’esercizio?
•  Si realizzi un caso di test che crea un Folder che contiene due File
(da 100 e 5000 byte) e un ulteriore Folder vuoto, e verifica il
funzionamento di getSize e isBiggerThan
14
Rappresentazione UML delle
classi dell’esercizio 4
File
0..*
Folder
Polimorfismo: esercizio 5
(polimorfismo con Object)
•  Si implementi il metodo equals per le classi File e Folder
dell’esercizio precedentemente svolto, secondo l’ipotesi che due
File sono uguali se contengono esattamente gli stessi byte e due
Folder sono uguali se tutti gli oggetti contenuti nello stesso ordine
sono tutti uguali fra loro
•  Si realizzi un caso di test che crea tre Folder:
–  I primi due Folder contengono ciascuno due File: un File con 3 byte tutti
di valore 1 e un File con 5 byte di valori 1, 2, 3, 4 e 5.
–  Il terzo Folder è vuoto.
Il test usa il metodo equals per confrontare i primi due Folder tra loro
e il primo Folder con il terzo Folder, e verifica i risultati con
assertTrue/assertFalse.
Inoltre il test ripete il confronto tra i primi due Folder usando
assertEquals, senza invocare il metodo equals esplicitamente.
•  Domande: l’invocazione di assertEquals causa l’invocazione del
metodo equals di Folder? Se sì, com’è possibile che un metodo
della libreria JUnit invochi il metodo equals implementato
nell’esercizio?
15
Polimorfismo: esercizio 6
(estensione di sistemi esistenti)
•  Si personalizzi il framework AWT di Java (per la
produzione di interfacce grafiche) attraverso un oggetto
Label personalizzato: MyLabel.
•  La personalizzazione deve garantire che il testo delle
etichette di tipo MyLabel sia sempre concatenato con
una stringa (ad esempio “[Powered by …]”) qualsiasi sia
il testo impostato attraverso il costruttore
•  Suggerimento: si sfrutti l’overriding del metodo getText
•  Si realizzi un caso di test che crea una Label (istanziata
come oggetto di tipo MyLabel) e verifica il testo
dell’etichetta
•  Si crei una semplice interfaccia (classe Frame) che
visualizza un oggetto di tipo MyLabel
16