Interfacce • Interfacce come strumento di progetto Scelta delle classi di un progetto Criteri di coesione e accoppiamento Interfacce e riuso di codice 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 (count>0)? maximum : Double.NaN; } 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, ... { // ... } Esempio: 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 Domande • 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? • 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++; } Risposte • Deve implementare l’interfaccia Measurable ed il suo metodo getMeasure() deve restituire il valore della popolazione • La classe Object non ha un metodo getMeasure(), che viene invocato all’interno del metodo add() Polimorfismo – dynamic dispatch • Dynamic dispatch: Il metodo da invocare per rispondere ad un messaggio è deciso a tempo di esecuzione 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 • 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 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)”; } } 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) ); } } Domanda • Che cosa hanno in comune i meccanismi di overloading e di dynamic dispatch? In cosa sono diversi? Risposta • Entrambi i meccanismi contribuiscono a decidono quale metodo eseguire in risposta ad un messaggio, ma • • 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