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;.