Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Lezione 9: Design Pattern Comportamentali

3,719 views

Published on

✦ Design Pattern Comportamentali
• Observer (continuazione)
• State
• Strategy
• Visitor

Published in: Education, Technology
  • Be the first to comment

Lezione 9: Design Pattern Comportamentali

  1. 1. Lezione 25: Design Pattern Comportamentali Corso di Ingegneria del Software Laurea Magistrale in Ing. Informatica Università degli Studi di Salerno 1
  2. 2. Outline ✦ Design Pattern Comportamentali • Observer (continuazione) • State • Strategy • Visitor 2
  3. 3. Design Pattern Comportamentali 3
  4. 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. 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. 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. 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. 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. 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. 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
  11. 11. State ✦ Esempio di struttura 11
  12. 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. 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. 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. 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. 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. 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. 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. 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
  20. 20. Strategy ✦ Esempio di struttura 20
  21. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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. 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
  37. 37. Visitor ✦ Esempio di struttura 37
  38. 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. 39. Visitor ✦ Esempio di soluzione (continua) public interface UserVisitor { void visitAnonymous(AnonymousUser user); void visitRegular(RegularUser user); void visitDeluxe(DeluxeUser user); } 39
  40. 40. Visitor ✦ Esempio di soluzione (continua) public class AnonymousUser extends User { public void accept(UserVisitor visitor) { visitor.visitAnonymous(this); } } 40
  41. 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. 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. 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. 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. 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. 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. 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. 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

×