Programmazione con
Interfacce
• Scelta delle classi di un progetto
 Criteri di coesione e accoppiamento
• Interfacce
 Tipi, sottotipi e conversioni di tipo
 Polimorfismo e dinamic dispatch
• Interfacce come strumento di progetto
 Interfacce e riuso di codice
 Callbacks
• Classi interne
Organizzazione di una classe
public class ClassTemplate {
// costanti pubbliche
// costruttori pubblici
// metodi “accessors” pubblici
// metodi “mutators” pubblici
// campi privati
// classi interne e metodi ausiliari privati
}
Continua…
Organizzazione di una classe
• Visibilità dei campi: evitare campi pubblici
 Quando necessario, definite metodi per accedere e
modificare i campi
• Completezza dell’interfaccia pubblica
 Assicuratevi che l’interfaccia pubblica fornisca le
funzionalità necessarie per un utilizzo naturale ed
efficace delle istanze
• Documentate il codice
 Non solo commenti (vedremo …)
Classi di un progetto
• Una classe dovrebbe rappresentare un
concetto singolo, oppure un insieme di
valori ben caratterizzato (un tipo)
• Concetti/tipi in matematica:
Point, Rectangle, ..., Set, List, ...
• Concetti/tipi nella vita reale
BankAccount, CashRegister, ...
Continua…
Classi di un progetto
• Una classe può anche identificare un insieme
di “attori”
 oggetti che offrono un insieme di funzionalità
Scanner, RandomNumberGenerator
• Una classe può anche agire da
 contenitore di metodi e campi static (quindi una classe
senza istanze)
Math
 inizializzatore della computazione (mediante il main)
Coesione
• Una classe dovrebbe rappresentare un solo
concetto
• L’interfaccia pubblica di una classe è coesa
se tutte le sue componenti sono relative al
concetto che la classe rappresenta
Continua…
Coesione
• Questo è un esempio di classe non coesa …
public class CashRegister
{
public void enterPayment(int dollars, int quarters, int dimes,
int nickels, int pennies)
. . .
public static final double NICKEL_VALUE = 0.05;
public static final double DIME_VALUE = 0.1;
public static final double QUARTER_VALUE = 0.25;
. . .
}
• Perché?
Continua…
Coesione
• CashRegister coinvolge due concetti:
 Registratore di cassa, monete
• Soluzione alternativa: due classi
public class Coin
{
public Coin(double aValue, String aName){ . . . }
public String getName(){ . . . }
. . .
}
public class CashRegister
vararg
{
public void enterPayment(Coin... coins) { . . . }
. . .
}
Accoppiamento (Coupling )
• Una classe dipende da un’altra classe se
utilizza oggetti di quella classe
 CashRegister dipende da Coin per il calcolo della
somma del pagamento
 Coin non dipende CashRegister
Continua…
UML
• Per visualizzare le relazioni tra
classi utilizziamo diagrammi
• UML: Unified Modeling
Language.
• Notazione standard per
l’analisi ed il progetto di
applicazioni software
Gradi di accoppiamento
• Alto grado di accoppiamento implica molte
dipendenze tra classi
• Basso grado di accoppiamenento facilita
manutenibilità in caso di modifica delle
componenti (della loro interfaccia esterna)
Continua…
Gradi di accoppiamento
• Vedremo tecniche per bilanciare
correttamente il grado di accoppiamento
Interfacce
• L’impiego di tipi interfaccia nel progetto di
una applicazione rende il codice flessibile e
robusto
 favorisce il riuso di codice
 permette di controllare il grado di accoppiamento tra
le componenti del progetto
• Esempio: definiamo una classe DataSet che
permette di condurre alcune semplici analisi
su un insieme di dati numerici
 calcolo della media
 calcolo del valore massimo
Continua…
DataSet
public class DataSet
{
public void add(double x)
{
sum = sum + x;
if (count == 0 || maximum < x)
maximum = x;
count++;
}
public double getMaximum(){ return maximum; }
public double average()
{ return (count>0)? sum/count : Double.NaN; }
private double sum;
private double maximum;
private int count;
}
Interfacce
• Ora supponiamo di voler condurre le stesse
analisi su un insieme di
 conti bancari per
• tracciare la media degli importi del saldo
• calcolare il conto con il saldo massimo.
 monete per
• calcolare l taglio medio delle monete
• determinare il taglio massimo
• La struttura della classe è sempre la stessa,
ma il codice cambia …
Continua…
DataSet – versione per BankAccount
public class DataSet
{
public void add(BankAccount x)
{
sum = sum + x.getBalance();
if (count == 0 || maximum.getBalance() < x.getBalance())
maximum = x;
count++;
}
public BankAccount getMaximum() { return maximum; }
public double average()
{return (count>0)? sum/count : Double.NaN; }
private double sum;
private BankAccount maximum;
private int count;
}
DataSet – versione per Coin
public class DataSet
{
public void add(Coin x)
{
sum = sum + x.getValue();
if (count == 0 || maximum.getValue() < x.getValue())
maximum = x;
count++;
}
public Coin getMaximum() { return maximum; }
public double average()
{return (count>0)? sum/count : Double.NaN; }
private double sum;
private Coin maximum;
private int count;
}
Interfacce
riuso di codice
• Il meccanismo di analisi dei dati è sempre lo stesso;
la differenza è solo nel metodo che estrae i valori
• E se le diverse classi si uniformassero nell’uso di
uno stesso metodo, getMeasure() per fornire la
misura?
• In quel caso potremmo definire una sola versione di
DataSet con un metodo add() come il seguente
sum = sum + x.getMeasure();
if (count == 0 || maximum.getMeasure() < x.getMeasure())
maximum = x;
count++;
Continua…
Continua…
Interfacce
riuso di codice
• Quale è il tipo della variabile x a questo punto?
• Idea: il tipo di una qualunque classe che fornisca un
metodo getMeasure
• In Java usiamo tipi interfaccia
public interface Measurable
{
double getMeasure();
}
• Una dichiarazione di interfaccia include tutti i metodi
(le loro firme) che intendiamo attribuire al tipo
interfaccia corrispondente
DataSet – versione generica
public class DataSet
{
public void add(Measurable x)
{
sum = sum + x.getMeasure();
if (count == 0 || maximum.getMeasure() < x.getMeasure())
maximum = x;
count++;
}
public Measurable getMaximum() { return maximum; }
public double average() { return sum/count; }
private double sum;
private Measurable maximum;
private int count;
}
Classi vs. Interfacce
• Un tipo interfaccia è simile ad un tipo classe,
ma ci sono molte differenze importanti:
• I metodi (tutti) dell’interfaccia sono “astratti”
 L’interfaccia non definisce una implementazione
• Tutti i metodi di una interfaccia sono
automaticamente pubblici
• Una interfaccia non ha campi (mentre può
avere campi statici, ovvero costanti)
Classi implementano Interfacce
• La keyword implements indica che una
classe implementa una interfaccia
public class BankAccount implements Measurable
{
public double getMeasure()
{
return balance;
}
// altri metodi, campi e quant’altro ...
}
• Una classe può implementare più interfacce
 La classe deve implementare tutti i metodi di tutte le
interfacce che implementa
 tutti questi metodi devono essere public nella classe
Continua…
Classi implementano Interfacce
• Measurable Coins
public class Coin implements Measurable
{
public double getMeasure()
{
return value;
}
// . . .
}
Diagrammi UML
• Notazione:
 Le interfacce sono rappresentate utilizzando
• la tag «interface» associata al nome
• oppure indicando il nome in corsivo
• La relazione tra una interfaccia ed una classe che
la implementa è rappresentata da una freccia
tratteggiaga (dalla classe all’interfaccia)
 Notazione simile alla dipendenza: cambia la punta
della freccia
Continua…
Diagramma UML per l’esempio
implementa
dipende
Diagrammi UML
• L’impiego di interfacce riduce il numero di
classi ed il grado di accoppiamento tra le
classi
• Notate infatti che DataSet è disaccoppiata
da BankAccount e Coin
• Possiamo liberamente modificare le classi
 basta che implementino correttamente l’interfaccia
Sintassi: definizione di interfaccia
public interface InterfaceName
{
// firme e contratti per i metodi
}
Esempio:
public interface Measurable
{
double getMeasure();
}
Scopo:
Definire le firme ed i contratti per i metodi di un tipo interfaccia.
Tutti i metodi sono automaticamente pubblici.
Sintassi: dichiarazione di classe
public class ClassName
implements InterfaceName, InterfaceName, ...
{
// ...
}
Eesmpio:
public class BankAccount implements Measurable
{
//...
public double getMeasure()
{
// Method implementation
}
}
Scopo:
Fornire una implementazione per il tipo interfaccia
File DataSetTester.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
18:
/**
This program tests the DataSet class.
*/
public class DataSetTester
{
public static void main(String[] args)
{
DataSet bankData = new DataSet();
bankData.add(new BankAccount(0));
bankData.add(new BankAccount(10000));
bankData.add(new BankAccount(2000));
System.out.println("Average balance = "
+ bankData.getAverage());
Measurable max = bankData.getMaximum();
System.out.println("Highest balance = "
+ max.getMeasure());
Continua…
File DataSetTester.java
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32: }
DataSet coinData = new DataSet();
coinData.add(new Coin(0.25, "quarter"));
coinData.add(new Coin(0.1, "dime"));
coinData.add(new Coin(0.05, "nickel"));
System.out.println("Average coin value = "
+ coinData.getAverage());
max = coinData.getMaximum();
System.out.println("Highest coin value = "
+ max.getMeasure());
}
Continua…
File DataSetTester.java
Output:
Average
Highest
Average
Highest
balance = 4000.0
balance = 10000.0
coin value = 0.13333333333333333
coin value = 0.25
Domanda
•
Vogliamo utilizzare la classe DataSet per
trovare l’istanza di una classe Country con
la popolazione maggiore in un insieme.
Quali condizioni deve soddisfare la classe
Country?
Domanda
•
Cosa c’è di sbagliato nel seguente codice?
public void add(Object x)
{
sum = sum + x.getMeasure();
if (count == 0 || maximum.getMeasure() < x.getMeasure())
maximum = x;
count++;
}
Risposta
•
Deve implementare l’interfaccia
Measurable ed il suo metodo
getMeasure() deve restituire il valore
della popolazione
Risposta
•
La classe Object non ha un metodo
getMeasure(), che viene invocato
all’interno del metodo add()
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
• 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
Continua…
Conversioni di tipo
• È 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
Coin dime = new Coin(0.1, "dime");
Measurable y = dime; // OK
Continua…
Conversioni di tipo
• La conversione è lecita solo in determinate
situazioni
Measurable x = new Rectangle(5, 10, 20, 30); // ERRORE
• Problema: Rectangle non implementa
Measurable
Subtyping
• Subtyping (<:)
 Una relazione che permette di decidere quando è
legittimo convertire un tipo riferimento in un altro
• Chi decide cosa/quando è legittimo?
 il compilatore!
• Per il momento la regola è:
T1 <: T2 sse
 T1 è una classe, T2 è una interfaccia
 T1 implementa T2.
Continua…
Subtyping
• Principio di sostituibilità
 Un riferimento di un sottotipo può essere usato
ovunque ci si aspetti un riferimento di un supertipo
• Le regole di sottotipo devono garantire la
correttezza del principio di sostituibilità
Continua…
Subtyping
• La regola
 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
• 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)
Domanda
• Data l’implementazione generica della classe
DataSet, che oggetti possiamo passare
come argomento per x ?
public class DataSet
{
public void add(Measurable x) { ... }
...
}
Risposta
• Qualunque istanza di una una classe che
implementa Measurable
Cast
• Consideriamo
DataSet coinDS
coinDS.add(new
coinDS.add(new
. . .
Measurable max
= new DataSet();
Coin(0.25, "quarter"));
Coin(0.1, "dime"));
= coinDS.getMaximum();
• Cosa possiamo fare con max?
• Certamente non sapere quale moneta è …
String name = max.getName(); // ERRORE: Non ha tipo Coin
Continua…
Cast
Measurable max = coinDS.getMaximum();
• Noi sappiamo che max è un riferimento di
tipo Coin, ma il compilatore no.
• Dobbiamo usare un cast per convertire il
tipo interfaccia nel tipo classe
Coin maxCoin = (Coin) max;
• Ora usiamo maxCoin come desiderato
String name = maxCoin.getName();
Continua…
Cast
• 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
Coin maxCoin = (Coin) max;
• Se max non è un Coin errore a run time
Continua…
Cast
• Tipo statico e tipo dinamico di una variabile
 tipo statico: quello dichiarato
 tipo dinamico: il tipo del riferimento assegnato alla
variabile
• (T)var causa errore
 in compilazione
se T non è compatibile con il tipo statico di var
 in esecuzione
se T non è compatibile con il tipo dinamico di var
Continua…
Cast
Measurable m =
new BankAccount();
• OK: BankAccount sottotipo di Measurable
BankAccount b = (BankAccount) m
• Compila correttamente
 il tipo dichiarato di m è Measurable
 BankAccount e Measurable sono compatibili
• Esegue correttamente
 m è un BankAccount (il tipo dinamico di m è BankAccount)
Continua…
Cast
Measurable m =
new BankAccount();
• OK: BankAccount sottotipo di Measurable
Coin c = (Coin) m
• Compila correttamente
 il tipo dichiarato di m è Measurable
 Coin e Measurable sono compatibili
• Errore a run time
 m non è un Coin
Continua…
instanceof
• Permette di determinare il tipo dinamico di una
variabile
x istanceof T è true solo se x ha tipo dinamico T
• Quindi permette di evitare errori in esecuzione
if (x instanceof T)
return (T) x
• Esegue correttamente, perchè x è sicuramente un T
Domanda
•
Dato che sia BankAccount che Coin
implementano l’interfaccia Measurable, è
possibile convertire un riferimento di tipo
Coin ad uno di tipo BankAccount?
Risposta
•
No: un riferimento di tipo Coin può essere
convertito a Measurable, ma se poi
tentiamo di applicare un cast a
BankAccount, abbiamo una eccezione.
Domanda
•
E’ corretto un cast (BankAccount) x per
convertire una variabile x di tipo dichiarato
Measurable ad un riferimento di tipo
BankAccount?
Risposta
•
Solo se x contiene effettivamente un
riferimento ad un oggetto di tipo
BankAccount.
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…
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…
Polimorfismo – dynamic dispatch
• Detto anche late binding:
 deciso a tempo di esecuzione
• Diverso dal caso dell’overloading:
 Anche nel caso dell’overloading si deve decidere
quale versione del metodo invocare
 Ma qui la decisione viene risolta in compilazione
(early binding)
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…
Domanda
7. Che cosa hanno in comune i meccanismi di
overloading e di dynamic dispatch? In cosa
sono diversi?
Risposte
7. Entrambi i meccanismi decidono quale
metodo eseguire in risposta ad un
messaggio, ma
•
•
Nell’overloading la scelta è fatta in compilazione
guardando il tipo dei parametri
Nel dynamic dispatch la scelta è fatta in esecuzione
guardando il tipo dell’oggetto che riceve il
messaggio
Esempio
•
Costruiamo una applicazione per disegnare
un insieme di forme geometriche contenute
in una componente grafico:


definiamo una JComponent contenitore per un
insieme di forme geometriche disegnate mediante
una invocazione del metodo paintComponent()
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
. . .
}
}
ShapeComponent
•
Un contenitore di Cars e Smileys
•
Un caso particolare di JComponent
/**
Una component che contiene un insieme Cars e Smileys
*/
class ShapeComponent extends JComponent
{
/**
Disegna tutte le forme di questo component
*/
public void paintComponent(Graphics g){ /* ... */ }
}
Domanda
• Che struttura utilizziamo per memorizzare le
forme contenute nella ShapeComponent?
• Come definiamo il metodo
paintComponent() in modo che disegni
tutte le forme della componente?
Risposte
ArrayList:
• definiamo una nuova interfaccia: Shape
interface Shape { void draw(Graphics2D g); }
• Ridefiniamo le classi Car e Smiley in modo
che implementino Shape
• Memorizziamo gli oggetti della componente
in una ArrayList<Shape>
Car e Smiley implementano Shape
class Car implements Shape
{
. . .
public void draw(Graphics2D g)
{
// Istruzioni per il disegno
. . .
}
}
class Smiley implements Shape
{
. . .
public void draw(Graphics2D g)
{
// Istruzioni per il disegno
. . .
}
}
ShapeComponent
Mantiene una ArrayList<Shape>
// Una component che contiene un insieme forme
class ShapeComponent extends JComponent
{
private ArrayList<Shape> shapes;
// crea una componente con un insieme di forme
public ShapeComponent(Shape... shapes) {
this.shapes = new ArrayList<Shape>();
for (Shape s:shapes) this.shapes.add(s);
}
// disegna tutte le componenti della componente
public void paintComponent(Graphics2D g2){
for (Shape c:shapes) c.draw(g2);
}
}
Diagramma delle Classi
Car
Smiley
ShapeComponent
Shape
Domanda
• E se volessimo disegnare solo le Shapes che
sono Cars?
Risposta
// disegna tutte le cars della componente
public void paintComponent(Graphics g){
Graphics2D g2 = (Graphics2D) g;
for (Shape c:shapes)
if (c instanceof Car) c.draw(g2);
}
Interfacce e “callbacks”
• La tecnica che abbiamo visto funziona per
classi di cui abbiamo il controllo
 Nel caso delle forme geometrice, possiamo rendere
Car e Smiley implementazioni di Shape
 Nel caso del DataSet rendiamo Measurable le
classi Coin e BankAccount
• Che facciamo se non possiamo modificare la
definizione delle classi?
Continua…
Interfacce e “callbacks”
Esempio:
• Vogliamo misurare l’area media e l’area
massima di un insieme di Rectangles
• Rectangle è una classe predefinita, e non
implementa Measurable …
Continua…
Interfacce e “callbacks”
• Callback: tradizionalmente (*) indica un
meccanismo per passare ad una funzione
un’altra funzione che esegue una qualche
operazione specifica
• La funziona passata come argomento si
definisce callback
(*) almeno dai tempi del progetto MIT che diede luogo a
X (meglio noto com X11) … correva l’anno 1984
Continua…
Interfacce e “callbacks”
Esempio classico:
• una funzione che gestisce le scelte di un
menù
• definita in termini di un parametro di tipo
funzione che viene invocata per gestire
l’operazione da eseguire in risposta alla
selezione degli items del menù
• Il parametro è una callback
Continua…
Interfacce e “callbacks”
• Nei linguaggi ad oggetti non possiamo
passare parametri di tipo funzione
• In C# esiste un costrutto predefinito che
realizza questo meccanismo: i delegates
• In Java il meccanismo può essere simulato
 definiamo una classe con un metodo che implementa
la funzione callback
 passiamo un oggetto di quella classe e lo utilizziamo
per invocare la callback
Callbacks per DataSet
• Problema: misurare l’area media e l’area
massima di un insieme di Rectangles
• Nell’implementazione vista, gli oggetti da
misurare offrono direttamente il metodo che
misura
• Alternativa: passiamo alla classe DataSet
un metodo per misurare oggetti
Continua…
Callbacks per DataSet
public interface Measurer
{
double measure(Object anObject);
}
• measure() misura qualunque oggetto e Measurer
rappresenta qualunque classe lo definisca
• Object è il supertipo di tutti i tipi riferimento
• qualunque riferimento si può passare per un
parametro di tipo Object
Continua…
Callbacks per DataSet
• La classe DataSet diventa dipendente dal
metodo di misura, ovvero da qualunque
classe che definisca questo metodo
public class DataSet
{ ...
public DataSet(Measurer aMeasurer)
{
measurer = aMeasurer;
}
...
private Measurer measurer;
}
Continua…
Callbacks per DataSet
• Ora il metodo add() richiede la misura al
measurer non all’oggetto che viene incluso
nel dataset.
public void add(Object x)
{
sum = sum + measurer.measure(x);
if (count == 0 || measurer.measure(maximum) < measurer.measure(x))
maximum = x;
count++;
}
Continua…
Callbacks per DataSet
• Possiamo definire Measurers per qualunque
tipo di misura, in particolare per misurare
Rectangles
public class RectangleMeasurer implements Measurer
{
public double measure(Object anObject)
{
if (!anObject instanceof Rectangle) return Double.NaN;
Rectangle aRectangle = (Rectangle) anObject;
double area = aRectangle.getWidth() * aRectangle.getHeight();
return area;
}
}
Continua…
Callbacks per DataSet
• Notiamo il cast da Object a Rectangle
Rectangle aRectangle = (Rectangle) anObject;
• Passiamo il measurer desiderato al momento della
costruzione del dataset
Measurer m = new RectangleMeasurer();
DataSet data = new DataSet(m);
data.add(new Rectangle(5, 10, 20, 30));
. . .
• Dynamic dispatch => invochiamo l’implementazione
di measure() fornita da RectangleMeasurer
Continua…
Diagramma delle classi
• Notate che la classe Rectangle è
disaccoppiata dall’interfaccia Measurer
double measure(Object o)
Raffronto tra le due soluzioni
• Callbacks
Measurer
double measure(Object o)
Rectangle
• Oggetti
Measurable
double getMeasure()
File DataSet.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
/**
Calcola la media di un insieme di oggetti.
*/
public class DataSet
{
/**
Costruisce un insieme vuoto con un dato misuratore.
@param aMeasurer il misuratore utilizzato per
misurare i valori
*/
public DataSet(Measurer aMeasurer)
{
sum = 0;
count = 0;
maximum = null;
measurer = aMeasurer;
}
Continua…
File DataSet.java
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
/**
aggiunge un valore al dataset.
@param x un dato
*/
public void add(Object x)
{
sum = sum + measurer.measure(x);
if (count == 0
|| measurer.measure(maximum)
< measurer.measure(x))
maximum = x;
count++;
}
/**
Calcola la media dei dati considerati.
@return la media o 0 se l’insieme di dati è vuoto
*/
Continued…
File DataSet.java
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
public double average()
{
if (count == 0) return 0;
else return sum / count;
}
/**
Il massimo del dataset.
@return il massimo o 0 se non il dataset è vuoto
*/
public Object maximum()
{
return maximum;
}
Continua…
File DataSet.java
50:
51:
52:
53:
54: }
private
private
private
private
double sum;
Object maximum;
int count;
Measurer measurer;
File DataSetTester.java
01:
02:
03:
04:
05:
06:
07:
08:
09:
10:
11:
12:
13:
14:
15:
16:
17:
import java.awt.Rectangle;
/**
Dimostra l’uso del misuratore.
*/
public class DataSetTester
{
public static void main(String[] args)
{
Measurer m = new RectangleMeasurer();
DataSet data = new DataSet(m);
data.add(new Rectangle(5, 10, 20, 30));
data.add(new Rectangle(10, 20, 30, 40));
data.add(new Rectangle(20, 30, 5, 10));
Continua…
File DataSetTester.java
18:
19:
20:
21:
22: }
System.out.println("Average area = " + data.getAverage());
Rectangle max = (Rectangle) data.getMaximum();
System.out.println("Maximum area rectangle = " + max);
}
File Measurer.java
01: /**
02:
Interfaccia di qualunque classe le cui istanze misurano
altri oggetti.
03: */
04: public interface Measurer
05: {
06:
/**
07:
Calcola la misura di un oggetto.
08:
@param anObject l’oggetto da misurare
09:
@return la misura
10:
*/
11:
double measure(Object anObject);
12: }
File RectangleMeasurer.java
01: import java.awt.Rectangle;
02:
03: /**
04:
le istanze di questa classe misurano
l’area di un rettangolo
05: */
06: public class RectangleMeasurer implements Measurer
07: {
08:
public double measure(Object anObject)
09:
{
10:
Rectangle aRectangle = (Rectangle) anObject;
11:
double area = aRectangle.getWidth()
* aRectangle.getHeight();
12:
return area;
13:
}
14: }
15:
Continua…
File RectangleMeasurer.java
Output:
Average area = 616.6666666666666
Maximum area rectangle = java.awt.Rectangle[x=10,y=20,
width=30,height=40]
Domanda
•
Supponiamo di voler utilizzare la prima versione
della classe DataSet per trovare la Stringa più
lunga di un insieme dato in input. Quale è il
problema?
Risposta
•
Problema: la classe String non
implementa Measurable.
Domanda
•
Come possiamo utilizzare la seconda versione di
DataSet (con callbacks) per risolvere il problema
precedente?
Risposta
•
Definendo una classe StringMeasurer
che implementa l’interfaccia Measurer
Domanda
•
Perchè il metoto measure() dell’interfaccia
Measurer ha un parametro in più del metodo
getMeasure() dell’interfaccia Measurable?
Risposta
•
measure() misura un oggetto, passato
come argomento, mentre getMeasure()
misura this, ovvero il parametro implicito.
Esercizio
• Definiamo una nuova versione della classe
DataSet che sia utilizzabile sia su oggetti
generici, mediante un Measurer, sia su
oggetti Measurable.
• Il comportamento del metodo add() dipende
dal tipo dell’argomento:
 se il tipo è Measurable, l’argomento viene misurato
utilizzando il suo metodo getMeasure()
 in tutti gli altri casi, viene misutato dal Measurer del
data set
Callbacks nella gestione di un timer
• La classe javax.swing.Timer definisce
oggetti che generano eventi (ticks)
• Utile tutte le volte che vogliamo modificare
un oggetto ad intervalli di tempo regolari
• Ad esempio nelle animazioni:
 Smiley: modifica il saluto oppure l’espressione
 Car: si muove sul frame ad intervalli regolari
Continua…
Gestione di un timer
• Eventi notificati ad un “Action Listener”
associato al timer
• Action Listener descritto da una interfaccia
standard (predefinita)
public interface ActionListener
{
void actionPerformed(ActionEvent event);
}
 actionPerformed() invocato ad ogni tick
 event: contiene informazione sull’evento
Continua…
Gestione di un timer
• La gestione dell’evento avviene nel metodo
actionPerformed()
• Gestioni diverse realizzate da classi diverse
che implementano ActionListener
class MyListener implements ActionListener
{
public void actionPerformed(ActionEvent event)
{
// Eseguito ad ogni tick.
}
}
Continua…
Gestione di un timer
• Per associare un particolare listener al timer
è necessario registrare il listener sul timer
MyListener listener = new MyListener();
Timer t = new Timer(interval, listener);
tra due tick
• Ora possiamo far partire il timer
t.start(); // Esegue in un thread separato
Domanda
•
Quale è il ruolo del listener nel timer?
Risposta
•
Il listener implementa una callback: il
metodo actionPerformed() è la vera
callback che viene inclusa nel listener per
poter essere passata al controllore del
timer che la invoca ad ogni tick
Esempio: conto alla rovescia
• Un timer che esegue il countdown
File CountDown.java
class CountDown implements ActionListener
{
public CountDown(int initialCount)
{
count = initialCount;
}
public void actionPerformed(ActionEvent event){}
public void actionPerformed()
{
if (count >= 0) System.out.println(count);
if (count == 0) System.out.println("Liftoff!");
count--;
}
private int count;
}
Continua…
File TimeTester.java
import java.awt.event.*;
import javax.swing.*;
/**
Esemplifica la classe timer l’uso di action listeners.
*/
public class TimeTester
{
public static void main(String[] args)
{
CountDown listener = new CountDown(10);
final int DELAY = 1000;// Millisecondi tra due tick
Timer t = new Timer(DELAY, listener);
t.start();
JOptionPane.showMessageDialog(null, "Quit?");
System.exit(0);
}
}
Domanda
•
Quante volte viene chiamato il metodo
actionPerformed nel programma
precedente?
Risposta
•
Il metodo viene invocato una volta al
secondo.

Le prime 11 volte scrive un messaggio.

Le successive termina senza output,
decrementando il contatore. Il timer termina quando
l’utente chiude l’applicazione
NOTE
•
Tutti gli eventi del timer vengono gestiti dallo
stesso thread (l’Event Dispatch Thread)

•
se la gestione dell’evento richiede più tempo
dell’intervallo, gli eventi vengono persi
Informazione sull’utilizzo di timers in swing:

http://java.sun.com.j2se/1.5.0/docs/api/javax/swing/
Timer.html
Packages
• Package: insieme di classi e interfacce in relazione
• Per formare un package basta inserire la direttiva
package packageName;
come prima istruzione nel file sorgente
• Una sola direttiva per file
• Classi contenute in file che non dichiarano packages
vengono incluse in un package “anonimo”
 package anonimo OK solo per micro applicazioni, o in fase di
sviluppo
Continua…
Packages
Package
Finalità
Classe Tipica
java.lang
Supporto al linguaggio
Math, String
java.util
Utilities
Random
java.io
Input e Output
PrintStream
Java.awt
Abstract Window Toolkit
Color
Java.applet
Applets
Applet
Java.net
Networking
Socket
Java.sql
Accesso a database
ResultSet
Java.swing
Ingerfaccia utente Swing
JButton
…
…
…
Accesso agli elementi di un package
• Per accedere ai tipi di un package utilizziamo
il nome “qualificato”
java.util.Scanner in = new java.util.Scanner(System.in);
• Uso dei nomi qualificati verboso
• import permette sintesi
import java.util.Scanner;
. . .
Scanner in = new Scanner(System.in)
Import
• di una classe
import java.util.Scanner;
. . .
Scanner in = new Scanner(System.in)
• di tutte le classi di un package
import java.util.*;
Continua…
Import
• Packages non formano gerarchie
// import dei tipi di java.awt.color
import java.awt.color.*;
// import dei tipi di java.awt (non del package color!)
import java.awt.*;// import dei tipi di java.awt.
• Static import
 delle costanti e metodi statici dei tipi di un package
import static java.lang.Math.PI
import static java.lang.Math.*;.
Nomi di package
• Packages utili anche come “namespaces” per
evitare conflitti di nomi (per classi/interfacce)
• Esempio, Java ha due classi Timer
java.util.Timer vs. javax.swing.Timer
• Nomi di package devono essere univoci
 Convenzione: utilizziamo come prefissi domini
internet, oppure indirizzi e-mail (in ordine inverso)
it.unive.dsi
it.unive.dsi.mp
Continua…
Localizzazione di package
• Nomi di package devono essere consistenti
con i path della directory che li contengono
it.unive.dsi.mp.banking
• Deve essere contenuto in un folder/directory
localizzato nel path corrispondente
UNIX: <base directory>/it/unive/dsi/mp/banking
WINDOWS: <base directory>\it\unive\dsi\mp\banking
Continua…
Localizzazione di package
• CLASSPATH: definisce le directory base
dove localizzare i packages
• Spesso utili due directory base
 per file sorgenti (.java)
 per file compilati (.class)
UNIX:
export CLASSPATH=/home/mp/java/src:/home/mp/java/classes:.
WINDOWS:
set CLASSPATH=c:\home\mp\java\src;\home\mp\java\classes;.
Scarica

04InterfaceProgramming