Esercitazione sui Design Pattern - Intranet...
Transcript of Esercitazione sui Design Pattern - Intranet...
Esercitazione sui Design Pattern
Pattern Creazionali
Singleton
• Permette la creazione di una sola istanza della classe all’interno dell’applicazione
• Fornisce un metodo con cui ottenere l’istanza
• Il costruttore della classe non deve essere accessibile
Singleton
public class Logger { private final static Logger logger = new Logger();
private Logger() {}
public static Logger getInstance() { return logger; } public void log(Object o){}}
Singleton
public class Logger { private final static Logger logger = new Logger();
private Logger() {}
public static Logger getInstance() { return logger; } public void log(Object o){}}
Istanza e costruttore
privati
Singleton
public class Logger { private final static Logger logger = new Logger();
private Logger() {}
public static Logger getInstance() { return logger; } public void log(Object o){}}
Istanza e costruttore
privati
Metodo statico per accedere all’istanza
Singleton
public class Logger { private final static Logger logger = new Logger();
private Logger() {}
public static Logger getInstance() { return logger; } public void log(Object o){}}
Istanza e costruttore
privati
Metodo statico per accedere all’istanza
Metodi dell’istanza
Note
• Non deve essere usato per avere un riferimento globale a un oggetto.
• Vanno bene per creare oggetti ma non per accedervi
• Usatelo solo quando serve davvero!
• E se in futuro volessimo avere più istanze?
Factory
• Disaccoppia la creazione degli oggetti dall’invocazione del costruttore
• Permette di avere una logica che seleziona il tipo dinamico della nuova istanza
• Utili quando ci interessa avere una classe che implementi una certa interfaccia ma non ci interessa quale sia nello specifico
Esempio
• Abbiamo diversi tipi di bottoni, ciascuno specifico per un particolare sistema operativo
• A noi interessa solamente che venga costruito un bottone
• Vogliamo gestire nel modo più pulito possibile la creazione del bottone
Soluzione ‘brutta’
• Creazione della nuova istanza gestita dal chiamante
• La logica di creazione dell’oggetto viene mischiata con quella del suo utilizzo
• Possibili duplicazioni di codice. Va ripetuto ogni volta che ci serve un bottone
Factory
• La factory si occupa di istanziare il bottone del tipo corretto
• Possiamo separare il codice di selezione dell’istanza e quello che deve ‘usarla’
public class ButtonFactory { private final SupportedOperatingSystems os; public ButtonFactory(SupportedOperatingSystems os){ this.os = os; } public Button createButton() { switch (os) { case Windows: return new WindowsButton(); case Linux: return new LinuxButton(); } return null; }}
Factory
public class ButtonFactory { private final SupportedOperatingSystems os; public ButtonFactory(SupportedOperatingSystems os){ this.os = os; } public Button createButton() { switch (os) { case Windows: return new WindowsButton(); case Linux: return new LinuxButton(); } return null; }}
Factory Configurazione
public class ButtonFactory { private final SupportedOperatingSystems os; public ButtonFactory(SupportedOperatingSystems os){ this.os = os; } public Button createButton() { switch (os) { case Windows: return new WindowsButton(); case Linux: return new LinuxButton(); } return null; }}
Factory
Logica di creazione
delle istanze
Configurazione
Client
public class Client { public void useButton() { ButtonFactory factory = new ButtonFactory( SupportedOperatingSystems.Windows); Button button = factory.createButton(); }}
Client
public class Client { public void useButton() { ButtonFactory factory = new ButtonFactory( SupportedOperatingSystems.Windows); Button button = factory.createButton(); }}
L’utilizzatore non sa quale oggetto è stato creato
Note
• A volte anziché creare una classe è sufficiente un metodo statico, un factory method
Pattern Strutturali
Adapter
• Permette di far lavorare assieme classi che hanno interfacce non compatibili
• L’adapter incapsula la classe e fornisce l’interfaccia richiesta
Esempio
• Vogliamo sviluppare una applicazione che permetta l’utilizzo di diverse tecnologie di comunicazione, ad esempio Socket e RMI
• Ovviamente vogliamo evitare il più possibile duplicazioni di codice
Soluzione
• Scrivere la logica applicativa indipendentemente dal protocollo
• Implementare le classi per permettere la comunicazione
• Queste classi devono adattare il comportamento della classe a quello richiesto dal protocollo
Logica applicativa
public class ApplicationLogic { public void doSomething(){} public void doSomethingElse(){}}
Non implementa nulla relativamente ai protocolli di interazione con l’esterno
Adapterpublic class SocketAdapter { private final ApplicationLogic applicationLogic;
public SocketAdapter(ApplicationLogic applicationLogic) { this.applicationLogic = applicationLogic; }
public void messageReceived(String message) { if (message.equals("A")) { applicationLogic.doSomething(); } if (message.equals("B")) { applicationLogic.doSomethingElse(); } }}
Adapterpublic class SocketAdapter { private final ApplicationLogic applicationLogic;
public SocketAdapter(ApplicationLogic applicationLogic) { this.applicationLogic = applicationLogic; }
public void messageReceived(String message) { if (message.equals("A")) { applicationLogic.doSomething(); } if (message.equals("B")) { applicationLogic.doSomethingElse(); } }}
Logica vera e propria
Adapterpublic class SocketAdapter { private final ApplicationLogic applicationLogic;
public SocketAdapter(ApplicationLogic applicationLogic) { this.applicationLogic = applicationLogic; }
public void messageReceived(String message) { if (message.equals("A")) { applicationLogic.doSomething(); } if (message.equals("B")) { applicationLogic.doSomethingElse(); } }}
Logica vera e propria
Gestione del protocollo
Proxy
• Utile se vogliamo estendere, modificare o personalizzare le operazioni effettuate da una classe
• In genere permette di risolvere problemi di sicurezza o efficienza
Esempio
• Aggiungere alla logica applicativa una cache per migliorare l’efficienza nella risposta alle richieste degli utenti
• L’interfaccia della cache non cambia rispetto a quella della logica vera e propria
Logica applicativa
public class ApplicationLogic implements ApplicationInterface{ @Override public Object performOperation(Object parameters) {...}
Cachepublic class CachingProxy implements ApplicationInterface { private final ApplicationInterface application; private final Map<Object, Object> cache;
public CachingProxy(ApplicationInterface application) { this.application = application; cache = new HashMap<Object, Object>(); }
@Override public Object performOperation(Object parameter) { if (!cache.containsKey(parameter)) { cache.put(parameter,application.performOperation(parameter)); } return cache.get(parameter); }}
Cachepublic class CachingProxy implements ApplicationInterface { private final ApplicationInterface application; private final Map<Object, Object> cache;
public CachingProxy(ApplicationInterface application) { this.application = application; cache = new HashMap<Object, Object>(); }
@Override public Object performOperation(Object parameter) { if (!cache.containsKey(parameter)) { cache.put(parameter,application.performOperation(parameter)); } return cache.get(parameter); }}
Stessa interfaccia
Cachepublic class CachingProxy implements ApplicationInterface { private final ApplicationInterface application; private final Map<Object, Object> cache;
public CachingProxy(ApplicationInterface application) { this.application = application; cache = new HashMap<Object, Object>(); }
@Override public Object performOperation(Object parameter) { if (!cache.containsKey(parameter)) { cache.put(parameter,application.performOperation(parameter)); } return cache.get(parameter); }}
Stessa interfaccia
Diverso comportamento
Note
• L’utilizzo del proxy non richiede nessuna modifica nel client che lo deve utilizzare
• Altri esempi di proxy sono le versioni unmodifiable delle collections
• In quel caso i metodi sono re-implementati per inibire le operazioni di modifica
Decorator
• Permette di aggiungere funzionalità ad un oggetto
• Il nuovo comportamento può essere aggiunto a run time
• Non richiede la creazione di nuove sottoclassi
Esempio
• Scrivere una applicazione per rappresentare diversi tipi di caffè con diversi ingredienti
• Potremmo fare diverse sottoclassi, ma ne dovremmo fare troppe
• La soluzione delle sottoclassi non permetterebbe di aggiungere nuovi ingredienti a run time
Struttura basepublic interface Coffee { String getIngredients(); float getCost();}
public class SimpleCoffee implements Coffee{ @Override public String getIngredients() { return "Coffee"; }
@Override public float getCost() { return 5; }}
Classe di base, senza nessuna decorazione
Interfaccia “vista” dai client
Decorazionipublic abstract class CoffeeDecorator implements Coffee { private final Coffee decoratedCoffe;
public CoffeeDecorator(Coffee decoratedCoffee) { this.decoratedCoffe = decoratedCoffee; } @Override public float getCost(){ return decoratedCoffe.getCost(); }
@Override public String getIngredients() { return decoratedCoffe.getIngredients(); }}
Contiene un oggetto (anche con altre decorazioni) e permette di aggiungergli
decorazioni
Una decorazionepublic class Milk extends CoffeeDecorator {
public Milk(Coffee decoratedCoffee) { super(decoratedCoffee); }
@Override public float getCost() { return super.getCost() + .5f; }
@Override public String getIngredients() { return super.getIngredients() + " Milk"; }
}
Un’altra decorazionepublic class Whip extends CoffeeDecorator {
public Whip(Coffee decoratedCoffee) { super(decoratedCoffee); }
@Override public float getCost() { return super.getCost() + 1.0f; }
@Override public String getIngredients() { return super.getIngredients() + " Whip"; }
}
Come le usa il clientpublic class Client { public static void main(String args[]) { Coffee coffe = new SimpleCoffee(); Coffee decoratedCoffee = new Milk(coffe);
decoratedCoffee.getCost(); // 5.5 decoratedCoffee.getIngredients();// Coffee Milk decoratedCoffee = new Whip(decoratedCoffee); decoratedCoffee.getCost(); // 6.5 decoratedCoffee.getIngredients();// Coffee Milk Whip }}
Note
• Un approccio simile è usato nelle classi per l’input/output di Java: stream, writer, reader
• Oltre a modificare il comportamento è possibile anche aggiungere nuovi comportamenti
• Decorator è simile a Proxy, ma permette di comporre diversi comportamenti
Pattern Comportamentali
Strategy
• Permette di variare gli algoritmi utilizzati nell’implementazione della classe
• La classe base richiede una strategia esterna per portare a termine correttamente il suo compito
• L’abbiamo visto con l’ordinamento che richiede la sua strategia di comparazione tra una coppia di elementi
Esempio
• Vogliamo scrivere il codice per rappresentare un robot che può avere diverse strategie per gestire il suo comportamento
• Vogliamo fare sì che i comportamenti possano cambiare mentre la nostra applicazione è in esecuzione
Robotpublic class Robot { private Behavior behavior;
public Robot(Behavior behavior) { this.behavior = behavior; }
public void changeBehavior(Behavior behavior) { this.behavior = behavior; }
public void move() { behavior.move(); }}
Comportamentipublic interface Behavior { void move();}
public class DefensiveBehavior implements Behavior { @Override public void move() { System.out.println("Defensive behavior"); }}
public class AggressiveBehavior implements Behavior { @Override public void move() { System.out.println("Aggressive behavior"); }}
Note
• La strategia può definire anche solo una parte del comportamento della classe (Come nel caso dell’ordinamento)
Observer
• Utile per gestire il paradigma ad eventi
• Permette di gestire dinamicamente l’accoppiamento tra sorgenti di eventi e classi che devono reagire quando questi si verificano
Come funziona
• Si ha una classe “Osservabile” (in Java si può implementare l’interfaccia Observable)
• Si hanno degli oggetti “Osservatori” (in Java si può implementare l’interfaccia Observer)
Observable (Java)• Si trova nel package java.util
• È l’interfaccia implementata dal Subject
• Fornisce metodi per registrare gli Observer:
• addObserver(Observer o)
• removeObserver(Observer o)
• Fornisce metodi per notificare eventi agli Observer quando qualcosa nell’oggetto Observable cambia:
• notifyObserver(Object arg)
Observer (Java)
• È un’interfaccia implementata dalle classi che possono ricevere notifiche di eventi da parte di classi Observable.
• Ha un solo metodo:
•update(Observable o, Object arg)
• Il metodo è invocato quando si verifica un cambiamento nell’oggetto osservato.
Esempio: conto correntepublic class ObserverExample {
public static void main(String args[]) { Conto conto = new Conto(); SaldoObserver saldoObserver = new SaldoObserver(); conto.addObserver(saldoObserver); conto.aggiornaSaldo(30); }}public lass SaldoObserver implements Observer { public void update(Observable conto, Object saldo) { System.out.println("ricevuto aggiornamento saldo: " + saldo); }}public class Conto extends Observable { private Integer saldo = 0; public void aggiornaSaldo(Integer saldo) { this.saldo = saldo; setChanged(); notifyObservers(this.saldo); }}
Note
• Il pattern observer è utilizzato nel pattern Model View Controller per notificare alla View eventi generati nel Model e nel Controller.
Model-view-controller
Separation of concernsController
ModelView
Model
Incapsula lo stato dell’applicazione
Deve permettere di accedere ai dati
Deve notificare i cambiamenti dello stato
View
Deve mostrare il modello
Gestisce l’interazione con l’utente
Controller
Rappresenta la logica applicativa
Collega le azioni dell’utente con modifiche allo stato
Sceglie cosa deve essere mostrato
Come interagiscono?
Modifica lo stato
Controller
ModelView
Seleziona una vista
Azioni dell’utente
Notifica cambiamenti
Richiede informazioni
MVC: un possibile class diagram
Come si realizza?
Programmazione ad eventi per gestire la comunicazione tra i componenti
Eventi
Azioni dell’utente
Notifiche da model e controller
Sono delle classi e contengono delle informazioni dettagliate sull’evento
EsempioMouseEvent
Evento già definito nelle librerie grafiche di Java
Contiene informazioni sull’evento:tipo (Click, movimento, ...)
quale bottone è stato cliccatoposizione del puntatore
Ascoltatori
Si mettono in ascolto di un evento
Devono avere dei metodi per poter reagire agli eventi
Possono esserci più ascoltatori per un evento
Esempio
public interface MouseListener{ public void mouseClicked(MouseEvent e); public void mouseEntered(MouseEvent e); public void mouseExited(MouseEvent e); public void mousePressed(MouseEvent e); public void mouseReleased(MouseEvent e);}
Un listener deve implementare questa interfaccia e reagire nel modo opportuno agli eventi ricevuti
come parametri
Sorgenti di eventiNotificano gli eventi agli interessati
La notifica avviene invocando i metodi sugli ascoltatori
Devono avete un metodo per permettere la registrazione degli ascoltatori
Esempiopublic class EventSource{ private List<EventListener> listeners; public void registerListener(EventListener listener){ listeners.add(listener); }
private void notifyListeners(Event e){ for(EventListener listener : listeners){ listener.eventHappened(e); } }
public void foo(){ /* Viene generato un evento e inviata la notifica*/ notifyListeners(new Event(...)); }}
Logica ed interfaccia
Logica ed interfaccia utente devono essere separate
Servono due interfacce:Controller-Model/Viewaggiornare la visualizzazione
View/Controller-Modelinviare i comandi e le richieste
Cosa passare
SEMPRE oggetti che rappresentano eventi o creati appositamente
Oggetti modificabili del modello NON devono arrivare all’interfaccia
Soluzioni
Interfacce limitate (Solo metodi di visualizzazione)
Oggetti immutabili
Oggetti creati appositamente per la visualizzazione
Applicazioni distribuite
• Esempio di uso di MVC in una applicazione client/server in cui:
• la logica e i dati sono gestiti dal server
• l’interazione è gestita dal client
• Nota: il modo presentato non è l’unico!
Applicazioni distribuiteServer Client
Model View
Server Controller
Client Controller
Network AbstractionInterfaces
Possibile soluzione• Il Model viene implementato interamente nel
server.
• La View viene implementata interamente nel client.
• Il Controller viene implementato con un componente nel server e uno nel client.
• L’astrazione di rete mette a disposizione delle interfacce per permettere ai componenti del client di comunicare con i componenti del server.
Riconoscere un buon design
• Deve essere possibile modificare il tipo di rete (Socket o RMI) senza modificare Model e View.
• Deve essere possibile modificare il tipo di view (grafica o testuale) senza modificare Model, Server Controller, e le astrazioni di rete.
• Suggerimento: disaccoppiare la parti di Model/View/Controller (e le astrazioni di rete) utilizzando opportune interfacce e/o design pattern!