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
Scarica

Interfaces