Callbacks
• Scelta delle classi di un progetto
 Criteri di coesione e accoppiamento
• Interfacce e subtype polimorfismo
 Tipi, sottotipi e conversioni di tipo
 Polimorfismo e dinamic dispatch
• Interfacce come strumento di progetto
 Interfacce e riuso di codice
 Callbacks
• Classi interne
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;.