Esercitazione sui Design Pattern - Intranet...

Post on 28-Jul-2020

14 views 2 download

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!