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){
Istanza e Singleton costruttore privati 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 Singleton costruttore privati public class Logger { private final static Logger logger = new Logger(); private Logger() { public static Logger getinstance() { return logger; public void log(object o){ Metodo statico per accedere all istanza
Istanza e Singleton costruttore privati public class Logger { private final static Logger logger = new Logger(); private Logger() { public static Logger getinstance() { return logger; public void log(object o){ 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
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 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; Logica di creazione delle istanze
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
Adapter public 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();
Adapter Logica vera e propria public 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();
Adapter Logica vera e propria public 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(); 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) {...
Cache public 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);
Cache Stessa interfaccia public 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);
Cache Stessa interfaccia public 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); 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 base Interfaccia vista dai client public 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
Decorazioni public abstract class CoffeeDecorator implements Coffee { private final Coffee decoratedcoffe; public CoffeeDecorator(Coffee decoratedcoffee) { this.decoratedcoffe = decoratedcoffee; @Override public float getcost(){ return decoratedcoffe.getcost(); Contiene un oggetto (anche con altre decorazioni) e permette di aggiungergli @Override public String getingredients() { return decoratedcoffe.getingredients(); decorazioni
Una decorazione public 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 decorazione public 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 client public 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
Robot public 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();
Comportamenti public 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 corrente public 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 concerns Controller View Model
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? Controller Azioni dell utente Seleziona una vista Modifica lo stato View Richiede informazioni Notifica cambiamenti Model
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
Esempio MouseEvent Evento già definito nelle librerie grafiche di Java Contiene informazioni sull evento: tipo (Click, movimento,...) quale bottone è stato cliccato posizione 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 eventi Notificano gli eventi agli interessati La notifica avviene invocando i metodi sugli ascoltatori Devono avete un metodo per permettere la registrazione degli ascoltatori
Esempio public 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/View aggiornare la visualizzazione View/Controller-Model inviare 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 distribuite Server Client Server Controller Client Controller Network Abstraction Interfaces Model View
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!