1. Lezione 25: Design
Pattern Comportamentali
Corso di Ingegneria del Software
Laurea Magistrale in Ing. Informatica
Università degli Studi di Salerno
1
4. Observer
✦ Conseguenze
• il Subject è disaccoppiato dagli oggetti che
dipendono da esso (maggiore riusabilità e
testabilità)
• comunicazione “broadcast”: il Subject non sa
quanti oggetti dipendono da esso
4
5. Observer
✦ Note
• tutti gli Observer sono informati di tutte le
variazioni dello stato del Subject; in alcuni casi
questo può essere inefficiente
‣ una variante del pattern prevede che i possibili
cambiamenti di stato siano divisi in categorie (“Topic”), e
ciascun Observer specifica all’atto della registrazione quali
sono i topic a cui è interessato
5
6. Observer
✦ Observer vs Chain of Responsibility
• i due pattern soddisfano un’esigenza simile, però
‣ nella Chain of Responibility si assume che uno solo degli
handler gestisca effettivamente la richiesta; inoltre c’è una
priorità tra gli handler: ciascun handler ha la possibilità di
“filtrare” le richieste che arrivano agli handler successivi
‣ gli Observer invece sono tutti allo stesso livello, e
tipicamente tutti gli observer rispondono in qualche modo
a ciascun cambiamento (almeno per i Topic a cui sono
registrati)
6
7. State
✦ Il problema
• un oggetto (Context) può trovarsi in più “stati”
diversi, e il comportamento di alcune operazioni
deve dipendere dallo stato in cui l’oggetto si trova
• si vuole evitare che la dipendenza del
comportamento dallo stato sia “cablata”
all’interno dei metodi dell’oggetto perché ciò
renderebbe difficile cambiare/estendere l’insieme
degli stati
7
8. State
✦ Esempio del problema
• un oggetto che rappresenta un file può essere in
uno dei seguenti stati:
‣ chiuso
‣ aperto per operazioni di lettura
‣ aperto per operazioni di scrittura
‣ aperto per operazioni di lettura e scrittura
• le operazioni read e write, a seconda dello stato,
svolgono la loro funzione oppure lanciano
un’eccezione
8
9. State
✦ Esempio del problema
• in un programma di disegno l’utente può
selezionare diversi strumenti
‣ matita
‣ gomma
‣ pennello
‣ disegno di linee
‣ ecc.
• il programma deve rispondere agli eventi del
mouse in modo diverso a seconda dello strumento
selezionato
9
10. State
✦ Soluzione
• lo stato del Context viene trasformato in un
oggetto, che implementa un’interfaccia State con
le operazioni che devono essere eseguite
diversamente per ogni stato
• per ogni possibile stato si definisce una
sottoclasse concreta di State
• il Context mantiene un riferimento all’oggetto che
rappresenta lo stato corrente; per cambiare stato
viene cambiato questo riferimento
• il Context delega allo stato corrente le operazioni
che sono dipendenti dallo stato
10
12. State
✦ Esempio di soluzione
import java.awt.*;
public abstract class Tool {
public void mouseDown(Graphics g, int x, int y) {
}
public void dragTo(Graphics g, int x, int y) {
}
public void mouseUp(Graphics g, int x, int y) {
}
}
12
13. State
✦ Esempio di soluzione (continua)
import java.awt.Graphics;
public class LineTool extends Tool {
private int prevX, prevY;
public void mouseDown(Graphics g, int x, int y) {
prevX = x;
prevY = y;
}
public void mouseUp(Graphics g, int x, int y) {
g.drawLine(prevX, prevY, x, y);
}
}
13
14. State
✦ Esempio di soluzione (continua)
import java.awt.Graphics;
public class FreehandTool extends Tool {
private int prevX, prevY;
public void mouseDown(Graphics g, int x, int y) {
prevX = x;
prevY = y;
}
public void dragTo(Graphics g, int x, int y) {
g.drawLine(prevX, prevY, x, y);
prevX = x;
prevY = y;
}
public void mouseUp(Graphics g, int x, int y) {
g.drawLine(prevX, prevY, x, y);
}
}
14
15. State
✦ Esempio di soluzione (continua)
// . . .
public class Doodle extends Canvas {
private Tool selectedTool;
public Doodle() {
// . . .
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent evt) {
selectedTool.mouseDown(getGraphics(), evt.getX(), evt.getY());
}
// . . .
});
// . . .
}
// . . .
public void setTool(Tool t) {
selectedTool=t;
}
// . . .
}
15
16. State
✦ Conseguenze
• la dipendenza dallo stato non è distribuita nel
codice di diversi metodi del context, ma è raccolta
nei metodi delle implementazioni concrete di
State
• è semplice modificare il comportamento di uno
specifico stato, oppure aggiungere al sistema
nuovi stati inizialmente non previsti
16
17. Strategy
✦ Il problema
• spesso in un determinato contesto è necessario
effettuare un’operazione (o un insieme di
operazioni collegate) che può avere
implementazioni diverse
• si vuole rendere il contesto indipendente da una
particolare implementazione dell’operazione
17
18. Strategy
✦ Esempio del problema
• in un algoritmo di ordinamento basato su
confronti è necessario comparare due oggetti
• la comparazione dipende dal tipo di oggetti da
ordinare, ma anche per un singolo tipo di oggetti
sono possibili diversi criteri di ordinamento, che
corrispondono a diverse implementazioni
dell’operazione di comparazione
• si vuole rendere l’algoritmo di ordinamento
indipendente dal modo in cui viene effettuata la
comparazione
18
19. Strategy
✦ Soluzione
• si definisce una interfaccia Strategy che contiene
le operazioni di cui si vuole nascondere
l’implementazione
• per ogni implementazione si definisce una
sottoclasse concreta di Strategy
• l’oggetto che rappresenta il contesto (Context)
riceve un riferimento a un’istanza di una
sottoclasse concreta di Strategy
19
21. Strategy
✦ Esempio di soluzione
• L’interfaccia Comparator nella libreria Java
package java.util;
public interface Comparator {
// Confronta due oggetti
// restituisce un valore <0, =0 o >0
// a seconda che sia x<y, x=y, x>y
int compare(Object x, Object y);
// verifica se un altro oggetto è uguale
// a questo Comparator
boolean equals(Object other);
}
21
22. Strategy
✦ Esempio di soluzione (continua)
• Gli algoritmi di ordinamento nella libreria Java
ricevono come parametro un Comparator
public class Collections {
// Ordina una lista
public static List sort(List list, Comparator order) {
// . . .
22
23. Strategy
✦ Conseguenze
• è possibile realizzare in modo semplice famiglie di
algoritmi simili che differiscono per
l’implementazione di alcune operazioni
• il contesto può cambiare l’implementazione delle
operazioni anche a run time, scegliendo un
diverso oggetto Strategy
23
24. Strategy
✦ Template Method vs Strategy
• i due pattern risolvono lo stesso problema
• differenze:
‣ con Template Method non è necessario creare più oggetti
‣ con Strategy non c’è un’esplosione combinatoria di
sottoclassi se il contesto ha più operazioni che devono
essere incapsulate indipendentemente
‣ con Strategy l’implementazione delle operazioni
incapsulate può essere effettuata a run time
‣ una stessa Strategy può essere utilizzata in contesti diversi
24
25. Strategy
✦ Esempio di implementazione
• Rivediamo l’esempio delle operazioni di
accumulazione usando il pattern Strategy invece
di Template Method
• per cominciare, definiamo l’interfaccia Strategy
public interface AccumulationStrategy {
int initialValue();
int combine(int currentValue, int element);
}
25
26. Strategy
✦ Esempio di implementazione (continua)
• definiamo ora la classe Accumulator
public class Accumulator {
private AccumulationStrategy strategy;
public Accumulator(AccumulationStrategy strategy) {
this.strategy=strategy;
}
public int compute(int a[], int n) {
int value=strategy.initialValue();
int i;
for(i=0; i<n; i++)
value=strategy.combine(value, a[i]);
return value;
}
}
26
27. Strategy
✦ Esempio di implementazione (continua)
• definiamo ora le strategie concrete
public class SumStrategy implements AccumulationStrategy {
public int initialValue() {
return 0;
}
public int combine(int currentValue, int element) {
return currentValue+element;
}
}
27
28. Strategy
✦ Esempio di implementazione (continua)
• definiamo ora le strategie concrete
public class ProductStrategy implements AccumulationStrategy {
public int initialValue() {
return 1;
}
public int combine(int currentValue, int element) {
return currentValue*element;
}
}
28
29. Strategy
✦ Esempio di implementazione (continua)
• definiamo ora le strategie concrete
public class CountPositiveStrategy implements AccumulationStrategy
{
public int initialValue() {
return 0;
}
public int combine(int currentValue, int element) {
if (element>0)
return currentValue+1;
else
return currentValue;
}
}
29
30. Strategy
✦ Esempio di implementazione (continua)
• esempio di uso di Accumulator:
AccumulationStrategy strategy=new SumStrategy();
Accumulator acc=new Accumulator(strategy);
int sum=acc.compute(a,n);
30
31. Strategy
✦ State vs Strategy
• i due pattern sono molto simili (da un punto di
vista implementativo sono identici)
• la differenza è nella finalità:
‣ Strategy serve a parametrizzare un algoritmo, State serve
a semplificare l’implementazione di un comportamento
complesso
‣ Una Strategy generalmente non cambia durante l’esistenza
del Context (anche se è possibile farlo); uno State invece
quasi sempre cambia durante l’esecuzione
‣ Di solito il Context conosce tutti i possibili State che sono
applicabili (e gestisce il passaggio da uno State a un altro);
il Context invece non conosce l’insieme delle possibili
Strategy che potrà ricevere
31
32. Visitor
✦ Il problema
• in alcuni casi abbiamo una gerarchia di classi in
cui la gerarchia è stabile (non capita l’esigenza di
aggiungere nuove sottoclassi) ma l’insieme di
operazioni che devono essere effettuate in tutte le
classi è variabile (capita frequentemente di dover
aggiungere alla classe base nuove operazioni che
devono avere un’implementazione diversa in ogni
sottoclasse)
• i meccanismi della programmazione orientata agli
oggetti sono pensati per semplificare il caso
contrario: operazioni stabili, e insieme delle classi
variabile
32
33. Visitor
✦ Esempio del problema
• il nostro software gestisce il portale di una
community di utenti che possono appartenere alle
seguenti tre categorie:
‣ utente anonimo (non registrato)
‣ utente normale (registrato con iscrizione gratuita)
‣ utente “deluxe” (registrato con iscrizione a pagamento)
• il portale definisce una serie di operazioni il cui
funzionamento è diverso a seconda della
categoria di utente
• l’elenco delle operazioni disponibili cambia molto
frequentemente
33
34. Visitor
✦ Esempio del problema (continua)
• Ad esempio, l’operazione “Invia un messaggio”
può avere come comportamento:
‣ una segnalazione di errore per gli utenti anonimi
‣ l’invio condizionato alla verifica del numero massimo di
messaggi inviabili in un giorno per gli utenti regolari
‣ l’invio incondizionato per gli utenti deluxe
34
35. Visitor
✦ Nota
• Un problema di questo tipo potrebbe essere
risolto usando dei metodi con un if a cascata che
esamina il tipo dell’oggetto:
if (obj instanceof AnonymousUser)
// ...
else if (obj instanceof RegularUser)
// ...
else if (obj instanceof DeluxeUser)
// ...
else
throw new RuntimeException("Unvalid class!");
• in questo modo però il codice è meno chiaro, e c’è
una violazione del principio DRY
35
36. Visitor
✦ Soluzione
‣ si definisce una interfaccia/classe astratta Visitor, con un
metodo distinto per ogni classe concreta della gerarchia
‣ per ogni operazione da effettuare sugli oggetti (“elementi”)
della gerarchia, si definisce una sottoclasse concreta di
Visitor i cui metodi implementano il tipo di operazione da
effettuare su ciascun tipo di elemento
‣ nella classe base della gerarchia si definisce un metodo
astratto “accept” che riceve come parametro un Visitor
‣ le classi concrete della gerarchia implementano il metodo
accept richiamando il metodo del Visitor che corrisponde al
tipo di elemento in questione, a cui viene passato il
riferimento all’elemento corrente (this)
36
38. Visitor
✦ Esempio di soluzione
public abstract class User {
public String getIpAddress() {
return "10.0.77.1";
}
public abstract void accept(UserVisitor visitor);
}
38
39. Visitor
✦ Esempio di soluzione (continua)
public interface UserVisitor {
void visitAnonymous(AnonymousUser user);
void visitRegular(RegularUser user);
void visitDeluxe(DeluxeUser user);
}
39
40. Visitor
✦ Esempio di soluzione (continua)
public class AnonymousUser extends User {
public void accept(UserVisitor visitor) {
visitor.visitAnonymous(this);
}
}
40
41. Visitor
✦ Esempio di soluzione (continua)
public abstract class NamedUser extends User {
private String name;
public NamedUser(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
41
42. Visitor
✦ Esempio di soluzione (continua)
public class RegularUser extends NamedUser {
public static final int DAILY_CREDITS=100;
private int credits;
public RegularUser(String name) {
super(name);
credits=DAILY_CREDITS;
}
public void accept(UserVisitor visitor) {
visitor.visitRegular(this);
}
public int getCredits() {
return credits;
}
public void consumeCredits(int amount) {
credits -= amount;
}
public void restoreCredits() {
credits=DAILY_CREDITS;
}
}
42
43. Visitor
✦ Esempio di soluzione (continua)
public class DeluxeUser extends NamedUser {
private String creditCard;
public DeluxeUser(String name, String creditCard) {
super(name);
this.creditCard=creditCard;
}
public void accept(UserVisitor visitor) {
visitor.visitDeluxe(this);
}
public void pay(double amount) {
System.out.println("User "+getName()+" has paid "+amount+
" euros with card n. "+creditCard);
}
}
43
44. Visitor
✦ Esempio di soluzione (continua)
public class SendMessageVisitor implements UserVisitor {
public static final int MESSAGE_CREDITS=3;
private String receiver, body;
public SendMessageVisitor(String receiver, String body) {
this.receiver=receiver;
this.body=body;
}
public void visitAnonymous(AnonymousUser user) {
System.out.print(user.getIpAddress());
System.out.println(", non sei autorizzato a mandare messaggi!");
}
// continua ...
44
45. Visitor
✦ Esempio di soluzione (continua)
// ... continua
public void visitRegular(RegularUser user) {
if (user.getCredits()>=MESSAGE_CREDITS) {
System.out.print("Messaggio inviato da "+user.getName());
System.out.println(" a "+receiver+": "+body);
user.consumeCredits(MESSAGE_CREDITS);
} else {
System.out.print(user.getName());
System.out.println(", hai esaurito i crediti a tua disposizione.");
}
}
public void visitDeluxe(DeluxeUser user) {
System.out.println("Esimio commendator "+user.getName()+",");
System.out.println(" il suo messaggio: "+body);
System.out.println(+receiver);
System.out.println("Le porgiamo umilmente i nostri saluti.");
}
}
45
46. Visitor
✦ Esempio di soluzione (continua)
// ...
UserVisitor send=new SendMessageVisitor("Pippo", "ciao!");
User a=new AnonymousUser();
User b=new RegularUser("Paperino");
User c=new DeluxeUser("Gastone", "PAP131313");
a.accept(send);
b.accept(send);
c.accept(send);
// ...
46
47. Visitor
✦ Conseguenze
• è semplice aggiungere nuove operazioni che
abbiano un effetto diverso su ciascun tipo di
elemento
• il codice delle operazioni non contiene “if” a
cascata che controllano esplicitamente il tipo
dell’oggetto
• PROBLEMA: è difficile aggiungere nuovi tipi di
elemento (occorre cambiare tutti i Visitor)
47
48. Visitor
✦ Nota
• in un certo senso il pattern Visitor serve ad
aggirare una limitazione del polimorfismo in Java
(e nella maggior parte dei linguaggi OO):
‣ la scelta polimorfica del metodo da eseguire può dipendere
dal tipo di un solo oggetto, il ricevente (si parla di single
dispatch); invece negli esempi di applicabilità del pattern
Visitor si vuole fare in modo che il metodo eseguito
dipenda da DUE oggetti: l’elemento e l’operazione (double
dispatch)
• in alcuni linguaggi di programmazione (es.
Common Lisp, Dylan, Groovy, Perl 6) il multiple
dispatch è supportato direttamente dal
linguaggio, e non è quindi necessario questo
pattern
48