18/11/2009 UNICAM - p. 1/246
Design Pattern
Rosario Culmone
18/11/2009 UNICAM - p. 2/246
Perchè
I problemi incontrati nello sviluppare grossi progetti software sono spessoricorrenti e prevedibili.▲ I design pattern sono schemi utilizzabili nel progetto di un sistema.▲ Permettono quindi di non inventare da capo soluzioni ai problemi già risolti,
ma di utilizzare dei "mattoni" di provata efficacia.▲ Inoltre, un bravo progettista sa riconoscerli nella documentazione o
direttamente nel codice, e utilizzarli per comprendere in fretta i programmiscritti da altri. Quindi:● forniscono un vocabolario comune che facilita la comunicazione tra
progettisti;● svantaggio potenziale: possono rendere la struttura del codice più
complessa del necessario. Di volta in volta bisogna decidere se adottaresemplici soluzioni ad hoc o riutilizzare pattern noti;
● pericolo di "overdesign".
18/11/2009 UNICAM - p. 3/246
Vantaggi
▲ Notevole aumento della capacità di produrre software riutilizzabile;▲ Si danno allo sviluppatore strumenti utili per la modellazione di nuovi sistemi;▲ Si aumenta la documentazione e la chiarezza;▲ Si aumenta la velocità di sviluppo;▲ Si aumenta la robustezza del software;▲ Si aumenta la flessibilità e l’eleganza del software.
18/11/2009 UNICAM - p. 4/246
Design pattern
▲ Il termine "pattern" fu introdotto dall’architetto austriaco ChristopherAlexander negli anni ’70 (per la pianificazione di costruzioni in ambientiurbani).
▲ Nel 1987 Cunningham e Beck adattarono l’idea di Alexander per guidareprogrammatori inesperti in Smalltalk.
▲ Erich Gamma, tesi di dottorato, 1988-1991.▲ Dal 1990 al 1992 la famosa Gang of Four (Gamma, Helm, Johnson,
Vlissides) incominciò la stesura di un catalogo di pattern.▲ Nel 1995 la Gang of Four pubblicò "Design Patterns - Elements of Reusable
Object-Oriented Software" con la descrizione di 23 pattern.
18/11/2009 UNICAM - p. 5/246
Anti-pattern
La definizione di antipattern è stata coniata dalla "Gang of Four" per indicare itipici problemi che incorrono i programmatori nella scrittura del codice. Vi è unelenco esteso di "trappole" in cui cade il programmatore, eccone alcune:
Coltellino svizzero quando il programmatore prevede un numero elevato difunzionalità, la maggior parte inutile.
Fede cieca quando il programmatore presume la correttezza di un codice e nonprevede alcun controllo.
Azione a distanza quando elementi del programma che interagiscono sono postia distanza non controllando gli effetti della modifica di una parte sull’altra.
Reinventare la ruota quando il programmatore rinuncia ad adattare un moduloesistente riscrivendolo da capo e inserendo, presumibilmente, errori.
Spaghetti code quando si un uso eccessivo di costrutti per il controllo del flussorendendo praticamente illeggibile un codice e propenso agli errori.
Secondo gli autori, l’uso dei pattern contribuisce nell’evitare queste trappole.
18/11/2009 UNICAM - p. 6/246
Definizione
Un pattern è l’astrazione di un problema che si verifica nel nostro dominio,rappresentandone la soluzione in modo che sia possibile riutilizzarla pernumerosi altri contesti (Christopher Alexander).
▲ Descrizione di classi ed oggetti comunicanti adatti a risolvere un problemaprogettuale generale in un contesto particolare.(Gamma, Helm, Johnson, Vlissides, "Design Patterns: Elements of ReusableObject-Oriented Software", Addison-Wesley).
18/11/2009 UNICAM - p. 7/246
Struttura di un pattern
Nel libro dei GoF ogni pattern è descritto nel seguente modo:
Descrizione: una breve descrizione dell’obiettivo del pattern.
Esempio: si presenta un problema la cui soluzione si ottiene tramitel’applicazione del pattern.
Descrizione della soluzione offerta dal pattern: si descrive testualmentel’architettura del pattern e come questa si applica al problema.
Struttura del pattern: diagramma di classi in UML della struttura generica delpattern.
Applicazione del pattern: offre un diagramma UML delle classi del problema,presenta l’abbinamento delle classi del problema con le classi chedescrivono la struttura concettuale del pattern, descrive l’implementazionedel codice Java, presenta e commenta gli output dell’esecuzione.
Osservazioni sull’implementazione in Java: presenta gli aspetti particolariche riguardano l’implementazione del pattern in Java.
18/11/2009 UNICAM - p. 8/246
Proprietà
I pattern:▲ Costituiscono un vocabolario comune per i progettisti;▲ Sono una notazione abbreviata per comunicare efficacemente principi
complessi;▲ Aiutano a documentare l’architettura software;▲ Catturano parti critiche di un sistema in forma compatta;▲ Mostrano più di una soluzione;▲ Descrivono astrazioni software;▲ NON costituiscono una soluzione precisa di problemi progettuali;▲ NON risolvono tutti i problemi progettuali;▲ NON si applicano solo alla progettazione OO, ma anche ad altri domini.
18/11/2009 UNICAM - p. 9/246
Nome
Per identificare un pattern si utilizza un nome.▲ Il nome del pattern è molto utile per descrivere il problema, la sua soluzione
ed il suo uso;▲ Esso è composto da una o due parole;▲ Bisogna cercare di omogeneizzare i vocabolari personali di tutti i colleghi.
18/11/2009 UNICAM - p. 10/246
Problema
Un pattern si applica per risolvere un problema che si presenta nella fase dimodellazione.▲ Descrive quando applicare un pattern definendo il contesto ed il dominio di
appartenenza;▲ In generale include la lista di condizioni che devono essere valide per poter
giustificare l’uso di un determinato pattern.
18/11/2009 UNICAM - p. 11/246
Soluzione
Il pattern coinvolge componenti con particolari vincoli.▲ Descrive gli elementi che verranno usati durante la modellazione;▲ Descrive le relazioni e le responsabilità degli elementi;▲ E’ importante capire che la soluzione non rappresenta una specifica
implementazione o caso d’uso ma un modello che si applica a differentisituazioni.
18/11/2009 UNICAM - p. 12/246
Conseguenze
L’applicazione di un pattern mostra vantaggi e svantaggi.▲ Raccoglie l’elenco dei tempi e dei risultati;▲ E’ importante quando si devono prendere decisioni di modellazione;▲ Descrive varie metriche, i costi ed i tempi in relazione ai benefici che il
pattern introdurrebbe.
18/11/2009 UNICAM - p. 13/246
Scelta
Esistono numerosi pattern più o meno adatti al problema da modellare.▲ Esistono numerosi cataloghi di pattern;▲ Solitamente sono descritti attraverso una notazione comune "Design
Language";▲ E’ importante reperire il pattern adeguato per il proprio specifico dominio;▲ Considerare come un pattern risolve il problema: ogni pattern affronta il
probelma con una soluzione originale;▲ Considerare il suo intento: l’obiettivo del programma deve essere lo stesso
del pattern;▲ Studiare le interazioni tra pattern: i pattern generalmente sono
composizionali;▲ Considerare come deve variare il progetto: diversi approcci con diversi
pattern.
18/11/2009 UNICAM - p. 14/246
Design pattern nella libreria Java
I pattern sono utilizzati abbondantemente dalle classi standard di Java. Iteraror,Observer e molti altri sono stati introdotti già dalle prime versioni. I patterncalzano perfettamente e traggono i maggiori benefici dal polimorfismo edall’ereditarietà dei linguaggi di programmazione orientati agli oggetti.
18/11/2009 UNICAM - p. 15/246
Tipi di design pattern
I design patterns possono essere raggruppati secondo il pricipale contesto diapplicazione in:
Pattern di creazione delegati alla gestione della costruzione di oggetti;
Pattern di struttura delegati alla rappresentazione di oggetti;
Pattern di comportamento delegati al comportamento dinamico degli oggetti.
18/11/2009 UNICAM - p. 16/246
Pattern di creazione
Abstract Factory - Crea oggetti appartenenti a famiglie di classi senzaspecificare le classi concrete.
Builder - Separa il processo di creazione di un oggetto dalla rappresentazionedefinitiva.
Factory Method - Crea oggetti derivanti da diversi tipi di classi.
Prototype - Creazione di oggetti a partire da altri oggetti.
Singleton - Una classe di cui può esistere solo un singolo oggetto.
18/11/2009 UNICAM - p. 17/246
Pattern di struttura
Adapter - Realizza interfacce per differenti classi.
Bridge - Separa l’interfaccia di un oggetto dalla sua implementazione.
Composite - Oggetti composti da oggetti con la stessa struttura.
Decorator - Aggiunge dinamicamente funzionalità ad un oggetto.
Facade - Una singola classe che rappresenta un intero sottosistema.
Flyweight - Una rappresentazione fine di istanze efficentemente condivise.
Proxy - Un oggetto rappresenta un altro oggetto.
18/11/2009 UNICAM - p. 18/246
Pattern di comportamento
Chain of Responsibility - Un modo di passare richieste tra una catena dioggetti.
Command - Incapsula richieste di comandi ad un oggetto.
Interpreter - Un modo di includere elementi di linguaggio in un programma.
Iterator - Scansione degli elementi di una collezione.
Mediator - Definisce un modo semplificato di comuncazione tra classi.
Memento - Congela e ripristina lo stato interno di una classe.
Observer - Un modo di notificare cambiamenti ad un insieme di classi.
State - Modifica del comportamento degli oggetti al cambiamento dello stato.
Strategy - Incapsulamento di algoritmi nelle classi.
Template Method - Rimanda l’esatto passo di elaborazione di un algoritmo aduna sottoclasse.
Visitor - Aggiunge una nuova operazione senza cambiare la classe.
18/11/2009 UNICAM - p. 19/246
Pattern di creazione
Questi pattern permettono di descrivere come vengono creati gli oggetti.L’idea è di astrarre il modo in cui sono creati gli oggetti per dover fareesplicitamente new il meno possibile.Un pattern di creazione aiuta a rendere un sistema indipendente da come glioggetti sono creati, composti e rappresentati.
Esistono due temi ricorrenti circa i pattern di creazione:▲ Incapsulare la conoscenza circa quale classe concreta il sistema utilizzi;▲ Nascondere il modo in cui istanze di classi siano create e messe assieme.
18/11/2009 UNICAM - p. 20/246
Factory Method
Separa la responsabilità di istanziare una classe dalla responsabilità discegliere quale classe istanziare.
Noto come: Virtual Constructor.
18/11/2009 UNICAM - p. 21/246
Factory Method, scopo
Il design pattern Factory Method definisce un’interfaccia (Creator) per ottenereuna nuova istanza di un oggetto (Product) delegando ad una classe derivata(ConcreteCreator) la scelta di quale classe istanziare (ConcreteProduct).
La classe ConcreteCreator che determina quale classe ConcreteProductistanziare è stabilita a design-time attraverso l’ereditarietà, quindi questodesign pattern è classificato rispetto allo scopo come rivolto alle classi.Rispetto al fine questo design pattern è classificato tra i pattern di creazione.
18/11/2009 UNICAM - p. 22/246
Factory Method, applicabilità
Un componente od un framework può aver bisogno di delegare alprogrammatore che lo utilizza la scelta di quale classe istanziare. Ad esempio:▲ si può lasciare al programmatore la scelta di quale classe istanziare tra
quelle di una lista predefinita di classi del framework (configurazione);▲ si può lasciare al programmatore la scelta di istanziare una classe del
framework di default o una nuova classe da derivata da quella di default epersonalizzata dal programmatore stesso (personalizzazione);
▲ si può lasciare al programmatore la scelta di istanziare una nuova classe dalui realizzata (estensione);
Questa necessità è assolta dal design pattern Factory Method. Esso infattiinvece di richiamare direttamente il costruttore della classe da istanziareprevede l’uso di un metodo.
18/11/2009 UNICAM - p. 23/246
Factory Method, conseguenze
▲ Maggiore modularità: la concreta gestione delle operazioni di creazione egestione è confinata;
▲ Maggiore elasticità: è possibile aggiungere altri oggetti di tipo diverso senzacambiarne il loro uso;
▲ Maggiore flessibilità: il cliente usa diversi tipi di oggetti nello stesso modo.
18/11/2009 UNICAM - p. 24/246
Factory: UML
Product
Creator
FactoryMethod()
AnOperation()
ConcreteCreator
FactoryMethod()
ConcreteProduct
return new ConcreteProduct
product=FactoryMethod()
18/11/2009 UNICAM - p. 25/246
Comportamento
Product definisce l’interfaccia dell’oggetto creato dal factory method.
ConcreteProduct implementa l’interfaccia di Product.
Creator dichiara il factory method che produce un oggetto di tipo Product e lopuò invocare per creare un oggetto di tipo Product. Il creator può definireun’implementazione del factory method che produce un oggettoConcreteProduct di default.
ConcreteCreator ridefinisce il factory method per produrre un’istanza di unConcreteProduct
18/11/2009 UNICAM - p. 26/246
Factory Method. Esempio 1
Per esempio, ci sono due versioni A1 e A2 di una class A:
abstract class A { public abstract String getVal(); }
class A1 extends A {
private String val;
A1(String val) { this .val = val; }
public String getVal() { return "A1: " + val; }
}
class A2 extends A {
private String val;
A2(String val) { this .val = val; }
public String getVal() { return "A2: " + val; }
}
18/11/2009 UNICAM - p. 27/246
Factory Method. Esempio 2
La fattoria permette di astrarre come si fa la scelta fra A1 e A2:
class AFactory {
public static final int MAX_LENGTH = 3;
public AFactory() { } // Costruttore
public static boolean test(String s) {
return s.length() < MAX_LENGTH;
}
public static A get(String s) {
if (test(s)) { return new A1(s); }
return new A2(s);
}
}
18/11/2009 UNICAM - p. 28/246
Factory Method. Esempio 3
Adesso si creano gli oggetti di tipo A attraverso Factory
A a = AFactory.get("ab"), b = AFactory.get("abc");
System.out.println(a.getVal());
System.out.println(b.getVal());
È lo stesso codice, ma adesso a.getVal() produce A1: ab , inveceb.getVal() produce A2: abc .
VantaggiAbbiamo astratto la creazione di un oggetto di tipo A. Se si vuole cambiarecome sono creati gli oggetti di tipo A bisogna cambiare solamente la classeAFactory.
18/11/2009 UNICAM - p. 29/246
Abstract Factory
Il design pattern Abstract Factory definisce un’interfaccia ("AbstractFactory")tramite cui istanziare famiglie (famiglia 1, 2, ...) di oggetti (AbstractProductA,AbstractProductB, ), tra loro correlati o comunque dipendenti, senza indicareda quali classi concrete (ProductA1 piuttosto che ProductA2, ...). La sceltadelle classi concrete è delegata ad una classe derivata (ConcreteFactory1 perla famiglia 1, ConcreteFactory2 per la famiglia 2, ...).
Noto come: kit.
18/11/2009 UNICAM - p. 30/246
Abstract Factory, scopo
Fornire un’interfaccia per creare famiglie di oggetti dipendenti senza specificarele classi concrete.
18/11/2009 UNICAM - p. 31/246
Abstract Factory, applicabilità
▲ Realizzare un sistema indipendente da come i prodotti sono creati, compostie rappresentati;
▲ Il sistema deve essere configurato con famiglie multiple di prodotti;▲ Mettere a disposizione soltanto l’interfaccia, non l’implementazione, di una
libreria di classi.
18/11/2009 UNICAM - p. 32/246
Abstract Factory, conseguenze
▲ Isola le classi concrete;▲ Facilita la portabilità;▲ Aumenta la consistenza tra i prodotti;▲ Per contro, inserire nuovi prodotti risulta complicato, in quanto implica
cambiamenti all’Abstract Factory.
18/11/2009 UNICAM - p. 33/246
Abstract Factory: UML
Client
AbstractFactory
+createProductA()
+createProductB()
AbstractProductA
ConcreteFactory1
+createProductA()
+createProductB()
ConcreteFactory2
+createProductA()
+createProductB()
ProductA1 ProductA2
AbstractProductB
ProductB1 ProductB2
18/11/2009 UNICAM - p. 34/246
Abstract Factory. Esempio 1
Per esempio, date due versioni delle classe A e B:
abstract class A { }
class A1 extends A {
private String val;
A1(String val) { this .val = val; }
}
class A2 extends A {
private String val;
A2(String val) { this .val = val; }
}
18/11/2009 UNICAM - p. 35/246
Abstract Factory. Esempio 2
abstract class B { }
class B1 extends B {
private int val;
B1( int val) { this .val = val; }
}
class B2 extends B {
private int val;
B2( int val) { this .val = val; }
}
18/11/2009 UNICAM - p. 36/246
Abstract Factory. Esempio 3
Una fattoria ha il compito di dare un insieme compatibile di A e B:
abstract class AbAbstractFactory {
public abstract A getA(String val);
public abstract B getB( int i);
}
18/11/2009 UNICAM - p. 37/246
Abstract Factory. Esempio 4
Nel nostro caso A1 corrisponde a B1 e A2 a B2.Dunque ci sono due fattorie:
class AbAbstractFactory1 extends AbAbstractFactory {
public A getA(String val) { return new A1(val); }
public B getB( int i) { return new B1(i); }
}
e
class AbAbstractFactory2 extends AbAbstractFactory {
public A getA(String val) { return new A2(val); }
public B getB( int i) { return new B2(i); }
}
18/11/2009 UNICAM - p. 38/246
Abstract Factory. Esempio 5
Un esempio di utilizzo di queste fattorie è il seguente:
AbAbstractFactory f1 = new AbAbstractFactory1();
AbAbstractFactory f2 = new AbAbstractFactory2();
A a1 = f1.getA("ab"); // crea un oggetto di tipo A1
B b1 = f1.getB(1); // crea un oggetto di tipo B1
A a2 = f2.getA("ab"); // crea un oggetto di tipo A2
B b2 = f2.getB(2); // crea un oggetto di tipo B2
18/11/2009 UNICAM - p. 39/246
Singleton
Il design pattern Singleton permette di assicurare che una classe (Singleton)abbia una unica istanza e che questa sia globalmente accessibile in un puntoben noto.Il modo più semplice di implementare questo pattern in Java è di definire unaclass final con tutti i metodi statici.
18/11/2009 UNICAM - p. 40/246
Singleton, scopo
Costruire un unico oggetto di un determinato tipo.Assicurarsi che una classe abbia soltanto un’istanza, e fornirne un unico puntodi accesso.
18/11/2009 UNICAM - p. 41/246
Singleton, applicabilità
Il Singleton andrebbe usato:▲ Quando si vuole la garanzia che nel sistema vi sia una solo istanza di oggetti
di un determinato tipo;▲ Quando non si vuole delegare ad altri il controllo di unicità di un oggetto;▲ Quando più oggetti devono condividere un unico pool di dati;▲ Deve esserci esattamente una singola istanza di una classe, e deve essere
accessibile da un punto di accesso ben preciso;▲ Quando tale istanza deve essere estensibile tramite subclassing, i client
dovrebbero poter utilizzare l’istanza estesa senza modificare il propriocodice.
18/11/2009 UNICAM - p. 42/246
Singleton, conseguenze
▲ Accesso controllato all’istanza singola;▲ Name space ridotto;▲ Raffinamento di operazioni e rappresentazione delle stesse;▲ Possibilità di usare un numero variabile di istanze;▲ Maggiore flessibilità rispetto all’uso degli static member;
Quindi si ha:▲ Maggiore correttezza: anche volendo non è possibile produrre oggetti
distinti;▲ Maggiore modularità: la gestione dei dati gestisti dal singleton può essere
remotizzata;▲ Maggiore flessibilità: la modifica dei dati gestiti dal singleton non interferisce
con l’unicità dell’stanza.
18/11/2009 UNICAM - p. 43/246
Singleton: UML
Singleton
-uniqueInstance
-singletonData
-Singleton()
+getIstance()
+getSingletonData()
return uniqueIstance
18/11/2009 UNICAM - p. 44/246
Singleton
import java.io. * ;
class EsempioDiSingleton { // System
public static void main(String[] args) {
PrintStream o1 = System.out, o2 = System.out;
if (o1 == o2) o1.println("Single istance");
}
}
oppure
class EsempioDiSingleton { // Math
public static void main(String[] args) {
final double x = 2.0;
System.out.printf("sqrt(%f)=%f",x,Math.sqrt(x));
}
}
18/11/2009 UNICAM - p. 45/246
Singleton. Esempio 1
Ponendo il costruttore con visibilità privata di fatto si impedisce la creazione dioggetti. La creazione del singleton avviene solo con l’invocazione del metodogetIstance() che produce sempre il riferimento all’unica istanza. Ad esempio:
class Singleton {
private static Singleton uniqueIstance = null ;
private int singletonData;
private Singleton() { // generazione del dato unico
singletonData = ( int )(Math.random() * 10);
}
public static Singleton getSingleton() {
if (uniqueIstance== null ) uniqueIstance = new Singleton();
return uniqueIstance;
}
public int getSingletonData() { retun singletonData; }
}
18/11/2009 UNICAM - p. 46/246
Builder
Il design pattern Builder separa la costruzione (il metodo Construct dell’oggettoDirector è l’algoritmo di costruzione che compone le parti e ogni metodo diConcreteBuilder costruisce una parte) di un oggetto complesso dalla suarappresentazione (Product) in modo tale che lo stesso processo di costruzione(il metodo Construct dell’oggetto Director) possa essere usato per crearediverse rappresentazioni (per ogni rappresentazione ci sarà un suoConcreteBuilder che produce un diverso Product).Il builder è una variante della Abstract Factory in cui l’oggetto da produrre èComposite . Dunque la chiamata di un Builder può implicare la creazione didiversi oggetti. Il caso tipico è quello di un’applicazione con una interfacciagrafica. In tal caso c’è sempre un Builder che deve preoccuparsi dellacostruzione dell’interfaccia.
18/11/2009 UNICAM - p. 47/246
Builder, scopo
Separare la costruzione di un oggetto complesso dalla relativarappresentazione. Ovvero confina in una classe le operazioni per creareoggetti complessi mascherando a chi vuole ottenere oggetti i complessimeccanismi di costruzione.
18/11/2009 UNICAM - p. 48/246
Builder, applicabilità
▲ L’algoritmo per la creazione di un oggetto complesso dovrebbe essereindipendente dalle componenti dell’oggetto stesso;
▲ Il processo di costruzione consente differenti rappresentazioni per l’oggetto.
18/11/2009 UNICAM - p. 49/246
Builder, conseguenze
▲ E’ possibile variare la rappresentazione interna di un prodotto;▲ Isola il codice per la costruzione e la rappresentazione: il Builder incapsula il
modo in cui un oggetto complesso è costruito;▲ Consente un miglior controllo sul processo di costruzione: il Builder consente
una costruzione step-by-step del prodotto, sotto il controllo del Director.
18/11/2009 UNICAM - p. 50/246
Builder: UML
ClientDirector
+costructProduct()
Builder
product Product
+function()
+getProduct()
ConcreteBuilder
+function()
Product
-attrib
+function()
«call»
«instantiate»
«instantiate»
18/11/2009 UNICAM - p. 51/246
Struttura
Builder specifica l’interfaccia astratta che crea le parti dell’oggetto Product.
ConcreteBuilder costruisce e assembla le parti del prodotto implementandol’interfaccia Builder; definisce e tiene traccia della rappresentazione checrea.
Director costruisce un oggetto utilizzando l’interfaccia Builder.
Product rappresenta l’oggetto complesso e include le classi che definiscono leparti che lo compongono, includendo le interfacce per assemblare le partinel risultato finale.
18/11/2009 UNICAM - p. 52/246
Esempio Builder 1
/* Product */
class Pizza {
private String base = "", salsa = "", condimento = "";
public void setBase(String b) { base = d; }
public void setSalsa(String s){ salsa = s; }
public void setCondimento(String c) { condimento = c; }
}
18/11/2009 UNICAM - p. 53/246
Esempio Builder 2
/* Abstract Builder */
abstract class PizzaBuilder {
protected Pizza pizza;
public Pizza getPizza() { return pizza; }
public void createNewPizzaProduct() { pizza = new Pizza(); }
public abstract void buildBase();
public abstract void buildSalsa();
public abstract void buildCondimento();
}
18/11/2009 UNICAM - p. 54/246
Esempio Builder 3
/* ConcreteBuilder */
class PizzaMargherita extends PizzaBuilder {
public void buildBase() { pizza.setBase("sottile"); }
public void buildSalsa() { pizza.setSauce("pomodoro"); }
public void buildCondimento() {
pizza.setCondimento("acciughe");
}
}
18/11/2009 UNICAM - p. 55/246
Esempio Builder 4
/* ConcreteBuilder */
class PizzaCapricciosa extends PizzaBuilder {
public void buildBase() { pizza.setBase("spessa"); }
public void buildSalsa() { pizza.setSalsa("salsa"); }
public void buildCondimento() {
pizza.setCondimento("uova+olive+carciofini");
}
}
18/11/2009 UNICAM - p. 56/246
Esempio Builder 5
/* Director */
class Cottura {
private PizzaBuilder pizzaBuilder;
public void setPizzaBuilder(PizzaBuilder pb) {
pizzaBuilder = pb;
}
public Pizza getPizza() {
return pizzaBuilder.getPizza();
}
public void constructPizza() {
pizzaBuilder.createNewPizzaProduct();
pizzaBuilder.buildBase();
pizzaBuilder.buildSalsa();
pizzaBuilder.buildCondimento();
}
}
18/11/2009 UNICAM - p. 57/246
Esempio Builder 6
/* Client */
class Cuoco {
public static void main(String[] args) {
Cottura cuoce = new Cottura(); // 1
PizzaBuilder pizzaMargherita = new PizzaMargherita(); // 2
PizzaBuilder pizzaCapricciosa = new PizzaCapricciosa(); // 3
cuoce.setPizzaBuilder(pizzaMargherita); // 4
cuoce.constructPizza(); // Attiva il costruttore // 5
Pizza pizza = cuoce.getPizza(); // 6
}
}
18/11/2009 UNICAM - p. 58/246
Esempio Builder 7
1. Il Cuoco (client), crea una istanza di Cottura indipendente da che cosacuocerà
2. Il Cuoco crea una istanza di pizzaMargherita (ConcreteBuilder)3. Il Cuoco crea una istanza di pizzaCapricciosa (ConcreteBuider)4. Il Cuoco assegna a cuoce, istanza di Cottura, la pizzaMargherita
5. Il Cuoco invoca il costruttore di cuoce per cuocere la pizzaMarcheritaassegnata prima
6. Il Cuoco invoca i metodi di cuoce per accedere a funzionalità di specifiche dipizzaMargherita (Product)
18/11/2009 UNICAM - p. 59/246
Prototype
Il design pattern Prototype istanzia un oggetto clonandolo da un’istanzaesistente.Esso si applica quando la creazione di un oggetto di tipo A è costosa in terminidi computazione ma può essere semplificata avendo già un oggetto di tipo A. InJava, nella class Object è previsto un metodo clone .
18/11/2009 UNICAM - p. 60/246
Protopype, scopo
Specificare il tipo di oggetti da creare utilizzando un’istanza prototipale, ecreare nuovi oggetti copiando il prototipo.
18/11/2009 UNICAM - p. 61/246
Prototype, applicabilità
Il pattern andrebbe usato nel caso in cui un sistema dovrebbe essereindipendente da come i prodotti sono creati, e:▲ Quando le classi da istanziare sono specificate a run-time;▲ Per evitare la creazione di gerarchie di factory parallele alle gerarchie di
prodotti;▲ Quando le istanze di una classe possono trovarsi in soltanto una (o poche)
combinazioni di stati.
18/11/2009 UNICAM - p. 62/246
Prototype, conseguenze
(oltre a quelle dell’Abstract Factory e del Builder):▲ Aggiungere o rimuovere prodotti a run-time;▲ Specificare nuovi oggetti variando valori;▲ Specificare nuovi oggetti variando strutture;▲ Ridurre il sub-classing;▲ Caricare classi dinamicamente nell’applicazione.
18/11/2009 UNICAM - p. 63/246
Prototype: UML
Prototype
+clone()
Client
+operation()
ConcretePrototype1
+clone()
ConcretePrototype2
+clone()
return this return this
Object x = prototype.clone()
«use»
18/11/2009 UNICAM - p. 64/246
Protopype. Esempio 1
class Object {
// ...
protected Object clone() throws CloneNotSupportedException {
if (! ( this instanceof Cloneable)) {
throw new CloneNotSupportedException();
}
// ....
}
}
18/11/2009 UNICAM - p. 65/246
Protopype. Esempio 2
Per essere duplicato un oggetto deve implementare l’interfaccia Cloneable(che è vuota!). Il comportamento di default è di copiare l’oggetto ma non gliattributi.Per esempio, per permettere di copiare una class A fuori di A bisogna fare unoverriding del metodo clone:
class Element implements Cloneable {
private int i;
int getI() { return i; }
void setI( int i) { this .i = i; }
public Object clone() throws CloneNotSupportedException {
return super .clone();
}
}
18/11/2009 UNICAM - p. 66/246
Protopype. Esempio 3
Adesso si può creare un prototipo:
class Use {
public operation() {
try {
Eelement [] array = new array[100];
Element one = new Element();
array[0] = one;
one.setI(0);
for ( int i = 1, i < 100; i++) {
array[i]=one.clone(); array[i].setI(i);
}
} catch (CloneNotSupportedException e) {
}
}
18/11/2009 UNICAM - p. 67/246
Pattern di struttura
I pattern di struttura si applicano per descrivere come è stata organizzata lastruttura dei diversi oggetti. Essi riguardano il modo in cui classi e oggetti sonolegati tra loro in strutture più grandi.Questa organizzazione può essere fatta usando l’ereditarietà (si parla allora dipattern di classi) o usando oggetti che contengono altri oggetti (si parla alloradi pattern di oggetti).Il consiglio è di preferire sempre la seconda soluzione.
18/11/2009 UNICAM - p. 68/246
Adapter
Il design pattern Adatper converte l’interfaccia di una classe o di un oggetto(Adaptee) nell’interfaccia (Target) che l’utilizzatore (Client) si aspetta. Il designpattern Adatper permette all’utilizzatore (Client) e alla classe o oggetto(Adaptee) che altrimenti non potrebbero collaborare di lavorare insieme.Esso si applica quando c’è bisogno di adattare il comportamento di un oggettoB in modo tale che B propone la stessa interfaccia di un altro oggetto A.
Noto come: Wrapper.
18/11/2009 UNICAM - p. 69/246
Adapter, scopo
Convertire l’interfaccia di una classe in un’interfaccia differente richiesta dalclient: in tal modo è possibile l’interoperabilità tra classi aventi interfacceincompatibili.Si vuole usare una classe esistente senza modificarla. Tale classe è quella daadattare (adaptee). Il contesto in cui si vuole usare la classe adattata richiedeun’interfaccia detta obbiettivo che è diversa dalla classe che si vuole adattare.
18/11/2009 UNICAM - p. 70/246
Adapter, applicabilità
E’ consigliabile utilizzare l’adapter quando:▲ Occorre utilizzare classi esistenti, ma le interfacce non sono compatibili con
quella del client;▲ Occorre creare una classe riusabile che coopera con altre classi scollegate
da essa, e dunque aventi interfacce diverse;▲ Occorre utilizzare diverse sottoclassi esistenti, ma non è pratico adattare
l’interfaccia effettuando il subclassing di ognuna. Si può allora utilizzare unobject adapter;
▲ Non si vuole o non si può modificare la classe da adattare.
18/11/2009 UNICAM - p. 71/246
Adapter, conseguenze
▲ Un Class Adapter non è in grado di adattare una classe e tutte le relativesottoclassi;
▲ Un Class Adapter consente l’override del comportamento dell’Adaptee;▲ Un Object Adapter consente ad un singolo Adapter di operare con diversi
Adaptee;▲ Tramite un Object Adapter l’overriding del comportamento dell’Adaptee
risulta maggiormente difficoltoso: richiede il sub-classing dell’Adaptee, erichiede che l’Adapter punti a tali sub-classi piuttosto che all’Adaptee stesso.
18/11/2009 UNICAM - p. 72/246
Adapter: UML
ClientTarget
+request()
Adaptee
+specificRequest()
Adapter
+request()Nota
18/11/2009 UNICAM - p. 73/246
Adapter: UML
ClientTarget
+request()
Adaptee
+specificRequest()
Adapter
+request()Nota
adaptee
18/11/2009 UNICAM - p. 74/246
Adapter. Esempio 1
Per esempio, abbiamo costruito un’applicazione che permette di usare unoggetto di tipo A:
class A {
public int getX() { ... }
public int getY() { ... }
}
e vogliamo che si possa usare anche un oggetto B che con qualche modificapuò fare tutto quello che fa A.
class B {
int getXPlusY() { ... }
int getXMinuxY() { ... }
}
18/11/2009 UNICAM - p. 75/246
Adapter. Esempio 2
Il primo passo è di rappresentare quello che può fare A come un’interfaccia:
interface ACapable {
int getX();
int getY();
}
e cambiare A in conseguenza:
class A implements ACapable {
....
}
18/11/2009 UNICAM - p. 76/246
Adapter. Esempio 3
Adesso esistono due possibilità:Class AdapterSi può usare l’ereditarietà per adattare B:
class AClassAdapter extends B implements ACapable {
public int getX() {
return (getXPlusY() + getXMinusY()) / 2;
}
public int getY() {
return (getXPlusY() - getXMinusY()) / 2;
}
}
18/11/2009 UNICAM - p. 77/246
Adapter. Esempio 4
Object AdapterSi può creare un nuovo oggetto che contiene l’oggetto B:
class AObjectAdapter implements ACapable {
private B b;
public int getX() {
return (b.getXPlusY() + b.getXMinusY()) / 2;
}
public int getY() {
return (b.getXPlusY() - b.getXMinusY()) / 2;
}
}
Quest’ultima soluzione è da preferire.
18/11/2009 UNICAM - p. 78/246
In java
Adaptee InputStreamTarget ReaderAdapter InputStreamReaderClient La classe che vuole leggere testo da un flusso in ingressotargetMethod readadapteeMethod read
18/11/2009 UNICAM - p. 79/246
Bridge
Il bridge pattern permette di separare l’interfaccia di una classe (che cosa sipuò fare con la classe) dalla sua implementazione (come si fa). In tal modo sipuò usare l’ereditarietà per fare evolvere l’interfaccia o l’implementazione inmodo separato ed autonomo e sia possibile sostituire l’implementazione senzaconseguenze per l’utilizzatore (Client).
Noto come: Handle/Body, Driver.
18/11/2009 UNICAM - p. 80/246
Bridge, scopo
Disaccoppiare un’astrazione dalla relativa implementazione in maniera tale daconsentire ad entrambe di variare in maniera indipendente.
18/11/2009 UNICAM - p. 81/246
Bridge, applicabilità
Può essere conveniente utilizzare il bridge nei seguenti casi:▲ Si desidera evitare un binding permanente tra astrazione e interfaccia;▲ Occorre rendere flessibili astrazione e implementazioni mediante
sub-classing;▲ Le modifiche all’implementazione non dovrebbero avere impatto sui client;▲ Rendere l’implementazione completamente invisibile ai client;▲ Condividere un’implementazione tra oggetti multipli.
18/11/2009 UNICAM - p. 82/246
Bridge, conseguenze
▲ Disaccoppiare interfaccia e implementazione: ciò riduce anche necessità diricompliazioni continue dell’Abstraction durante la fase di sviluppo eincoraggia lo sviluppo di sistemi a layer;
▲ Migliorare l’estensibilità;▲ Nascondere i dettagli implementativi ai client.
18/11/2009 UNICAM - p. 83/246
Bridge: UML
Client
Abstraction
+operation()
Implementor
+operation()
RedefinedAbstractionConcreteImplementorA
+operationImp()
ConcreteImplementorB
+operationImp()
implementor.operationImp()
implementor
18/11/2009 UNICAM - p. 84/246
Bridge. Esempio 1
Vogliamo realizzare funzionalità e realizzazioni per disegnare cerchi. La parteche riguarda la sezione implementattiva è definita con una interfaccia chepermette successivamente di essere implementata con diverse classi
/* Implementor */
interface Cerchio {
public void disegnaCerchio( double x, double y, double raggio);
}
18/11/2009 UNICAM - p. 85/246
Bridge. Esempio 2
Due realizzazioni di implementazione
/* ConcreteImplementorA*/
class DisegnaCerchio1 implements Cerchio {
public void disegnaCerchio( double x, double y, double r) {
System.out.printf("Cerchio DisegnaCerchio1(%f,%f,&f) ",x,y,r);
}
}
e
/* ConcreteImplementorB*/
class DisegnaCerchio2 implements Cerchio {
public void disegnaCerchio( double x, double y, double r) {
System.out.printf("Cerchio DisegnaCerchio2(%f,%f,&f) ",x,y,r);
}
}
18/11/2009 UNICAM - p. 86/246
Bridge. Esempio 3
La parte che riguarda la sezione descrittiva è definita con una interfaccia chepermette successivamente di essere estesa con ulteriori funzionalità
/* Abstraction */
interface Disegno {
public void disegna();
public void ridimenzionaInPercentuale( double p);
// funzione aggiunta non definita nell’implementazione
}
18/11/2009 UNICAM - p. 87/246
Bridge. Esempio 4
/* RefinedAbstraction */
class DisegnaCerchio implements Disegno {
private double x, y, r;
private Cerchio d; // associazione implementor
DisegnaCerchio( double x, double y, double r, Cerchio i) {
this .x = x, this .y = y; this .r = r, this .d = i;
}
public void disegna() { d.disegnaCerchio(x,y,r); }
public void ridimenzionaInPercentuale( double p) { r * = p; }
}
}
18/11/2009 UNICAM - p. 88/246
Bridge. Esempio 5
/* Client */
class Bridge {
public static void main(String [] args) {
Disegno [] disegni = new Disegno[2];
disegni[0] = new DisegnaCerchio(1,2,3, new DisegnaCerchio1());
disegni[1] = new DisegnaCerchio(5,7,11, new DisegnaCerchio2());
for ( int i = 0; i < disegni.legth; i++) {
disegni[i].ridimensionaInPercentuale(2.5);
disegni[i].disegna();
}
}
}
18/11/2009 UNICAM - p. 89/246
Composite
Il composite pattern è applicato quando c’è bisogno di esprimere un insieme dioggetti che rappresentano una gerarchia.
18/11/2009 UNICAM - p. 90/246
Composite, scopo
Costruire oggetti che possono essere a loro volta componenti di oggetti dellostesso tipo.
Comporre oggetti in strutture ad albero per rappresentare gerarchie tutto-parti.Il Composite consente di trattare oggetti singoli e composizioni di oggetti inmaniera uniforme.
18/11/2009 UNICAM - p. 91/246
Composite, applicabilità
Il Composite dovrebbe essere usato quando:▲ Si desidera rappresentare gerarchie tutto-parti;▲ Si cerca di rendere i client capaci di ignorare le differenze tra insiemi di
oggetti ed oggetti individuali;▲ Si vogliono gestire un insieme di oggetti come se fossero singoli oggetti;▲ Ogni operazione dell’oggetto composto è la combinazione delle operazioni
dei singoli componenti;▲ Le funzionalità dell’oggetto composto sono le stesse dei suoi componenti.
18/11/2009 UNICAM - p. 92/246
Composite, conseguenze
▲ Sono definite gerarchie costituite da oggetti primitivi e oggetti composti;▲ Semplifica il client;▲ Rende semplice l’aggiunta di nuovi tipi di componenti;▲ Rende il design generale;
▲ Maggiore modularità: il pattern modulare per definizione;▲ Maggiore elasticità: i componenti possono essere sviluppati
successivamente;▲ Maggiore flessibilità: gli oggetti concreti possono realizzare le operazioni in
modo diverso;
▲ E’ però problematico porre dei vincoli sui componenti (es. specificare che unoggetto può essere composto solo da determinati componenti).
18/11/2009 UNICAM - p. 93/246
Composite, UML
Primitive
+operation()
Leaf
+operation()
Composite
+operation()
Invoca operation()per ciascun oggettoprimitivo e combinai risultati
18/11/2009 UNICAM - p. 94/246
Composite. Esempio 1
Una class astratta contiene tutto quello che si può fare con gli oggetti
class Primitive {
protected Vector componenti;
int getVolume();
}
Se si vuole realizzare un modello che realizzi la gestione di pacchi e scatoleche contengono pacchi e scatole.
18/11/2009 UNICAM - p. 95/246
Composite. Esempio 2
Dopo si possono creare degli oggetti semplici, i pacchi con il volume associato:
class Pacco extends Primitive {
private int volume;
public void putVolume( int v) { volume = v; }
public getVolume() { return volume; }
}
18/11/2009 UNICAM - p. 96/246
Composite. Esempio 3
o degli oggetti compositi, le scatole che contengono pacchi o altre scatole:
class Scatola extends Primitive {
public void putVolume( int v) { volume = v; }
public getVolume() {
int volume = 0;
for (Enumeration e = componenti.elements();
hasMoreElement(); )
volume += (Primitive)e.nextElement().getVolume();
return volume;
}
public addComponen(Primitive p) { componenti.add(p); }
}
18/11/2009 UNICAM - p. 97/246
Composite, esempio 4
Esempio di composite in java è:Primitive: ComponentComposite: ContainerLeaf: JButton, JTextAreaoperation(): getPreferedArea()
18/11/2009 UNICAM - p. 98/246
Decorator
Il design pattern Decorator aggiunge responsabilità in modo dinamico, ossia arun-time, ad un singolo oggetto senza richiedere l’uso della ereditarietà cheinvece aggiunge responsabilità alla classe, e quindi a tutti gli oggetti istanziati,ed in modo statico ossia già a design-time.Esso è applicato quando si vuole aggiungere una funzionalità ad una classesenza modificarla.
Noto come: Wrapper.
18/11/2009 UNICAM - p. 99/246
Decorator, scopo
Aggiungere responsabilità addizionali agli oggetti in maniera dinamica.
18/11/2009 UNICAM - p. 100/246
Decorator, applicabilità
Il Decorator andrebbe usato:▲ Quando le operazioni aggiunte dal decoratore non annullano le operazioni
effettuate dal decorato;▲ Quando è possibile aggiungere anche decorazioni nulle a tutte le operazioni
del decorato (il decoratore è un decorato);▲ Quando si vuole lasciare ad altri la libertà di decorare;▲ Per aggiungere responsabilità ad oggetti dinamicamente e
trasparentemente;▲ Per aggiungere responsabilità che possono essere eliminate;▲ Quando l’estensione tramite sub-classing si rivela poco pratica (es. un
cospicuo numero di estensioni produrrebbe un’esplosione delle sottoclassiper supportare ogni combinazione).
18/11/2009 UNICAM - p. 101/246
Decorator, conseguenze
▲ Evita che classi relative a feature specifiche siano presenti nella parte altadella gerarchia;
▲ Un Decorator e i suoi componenti non sono identici, anche se tutto ciò ètrasparente dal punto di vista logico;
▲ Usare i Decorator potrebbe però portare alla creazione di sistemi in cui sonopresenti troppi oggetti molto piccoli, che differiscono tra loro solo per i modiin cui sono interconnessi;
▲ Maggiore modularità: le decorazioni sono ereditate da tutte le sottoclassi deldecoratore e un decoratore è un componente;
▲ Maggiore elasticità: la decorazione può essere realizzata successivamentealla definizione del decorato;
▲ Maggiore flessibilità: le funzionalità aggiunte sono ereditabili da tutte lesottoclassi.
18/11/2009 UNICAM - p. 102/246
Decorator: UML
Component
+operation()
ConcreteComponent
+operation()
Decorator
+operation()
ConcreteDecoratorA
-addedState
+operation()
ConcreteDecoratorB
+operation()
+AddedBehaviour()
component.operation()
addBehaviour
18/11/2009 UNICAM - p. 103/246
Decorator, esempio 1
Si vuole realizzare una finestra e successivamente aggiungere le funzionalitàdi scroll orizontale e verticale.
/* Component */
interface Window {
public void draw();
public String getDescrition();
}
/* Decorator */
abstract class WindowDecorator {
protected Window decorateWindow; // associazione con Window
public windowDecorator(Window dw) {
this .decorateWindow = dw;
}
}
18/11/2009 UNICAM - p. 104/246
Decorator, esempio 2
Si realizzano due decorazioni
/* ConcreteDecoratorA */
class VerticalScrollBarDecorator extends WindowDecorator {
public VerticalScrollBarDecorator(Window dw) { super (dw); }
public void draw() {
drawVerticalScrollBar();
decorateWindow.draw();
}
private drawVerticalScrollBar() { /*codice per vert scroll */ }
public String getDescription() {
return decorateWindow.getDescrition() +
", including vertical scrollbar";
}
}
18/11/2009 UNICAM - p. 105/246
Decorator, esempio 3
La seconda decorazione introduce lo scrolling orizontale
/* ConcreteDecoratorB */
class HorizontalScrollBarDecorator extends WindowDecorator {
public HorizontalScrollBarDecorator(Window dw) { super (dw); }
public void draw() {
drawHorizontalScrollBar();
decorateWindow.draw();
}
private drawHorizontalScrollBar() { /*codice per horz scroll */ }
public String getDescription() {
return decorateWindow.getDescrition() +
", including horizontal scrollbar";
}
}
18/11/2009 UNICAM - p. 106/246
Decorator, esempio 4
La finestra senza scrollbar
/* ConcreteComponent */
class SimpleWindow extends Window {
public void draw() { /* codice per disegnare la finestra */ }
public String getDescription() {
return "simple window";
}
}
18/11/2009 UNICAM - p. 107/246
Decorator, esempio 5
Infine per usare Window con scrool
public class DecoratedWindowTest {
public static void main(String[] args) {
// create una finestra con scroll orizontale e verticale
Window decoratedWindow = new HorizontalScrollBarDecorator (
new VerticalScrollBarDecorator( new SimpleWindow()));
// stampa la descrizione della finestra
System.out.println(decoratedWindow.getDescription() );
}
}
18/11/2009 UNICAM - p. 108/246
Decorator in JDK
import java.io. * ;
class EsempioDiDecorator {
public static void main(String[] args) {
try {
InputStreamReader r = new InputStreamReader(System.in);
BufferedReader b = new BufferedReader(r);
System.out.print("Impostare elenco di caratteri: ");
System.out.println("Il primo carattere è: "+r.read());
System.out.println("Il resto: "+b.readLine());
} catch (IOException e) { System.out.println(e); }
}
}
18/11/2009 UNICAM - p. 109/246
Facade
Il design pattern Facade definisce un’iterfaccia unificata (Facade) di più altolivello attraverso cui accedere al sotto-sistema e alle sue varie interfaccierendendolo più semplice da usare.Un facade permette di concentrare in una sola classe un sistema composto dadiverse classi.
18/11/2009 UNICAM - p. 110/246
Facade, scopo
Fornire un’interfaccia unificata ad un insieme di interfacce di un sottosisistema,allo scopo di rendere il sottosistema più semplice da utilizzare.Creare una nuova interfaccia per un insieme di sottosistemi.
18/11/2009 UNICAM - p. 111/246
Facade, applicabilità
Il Facade andrebbe usato quando:▲ Si desidera fornire un’interfaccia semplificata ad un sottosistema complesso;▲ Esistono molte interdipendenze tra i client e le implementazioni delle
astrazioni: il facade effettua un’operazione di disaccoppiamento;▲ Realizzare sistemi a layer, in cui il facade costituisce punto di accesso ad un
layer (sottosistema);▲ Si vuole creare una nuova interfaccia per un insieme di sottosistemi;▲ Si voglioni rendere disponibili nuove operazioni che sono composizioni di
altre operazioni;
18/11/2009 UNICAM - p. 112/246
Facade, conseguenze
▲ “Scherma” il client dai componenti del sottosistema, riducendo il numero dioggetti che il client manipola;
▲ Favorisce un basso accoppiamento tra sottosistema e client;▲ In ogni caso, non proibisce ai client l’uso delle classi del sottosistema;▲ Crea una nuova interfaccia per un insieme di classi o di sottosistemi;▲ Permette di comporre in una nuova interfaccia un insieme di azioni di un
insieme di sottosistemi;▲ Rende indipendenti i sottosistemi dall’interfaccia che li compone.
18/11/2009 UNICAM - p. 113/246
Facade: UML
Facade
ClassA ClassB ClassC
Subsystem One Subsystem Two
18/11/2009 UNICAM - p. 114/246
Facade. Esempio 1
Un computer è composta da diversi sottosistemi complessi.
/* Subsystems */
class CPU {
public void freeze() { ... }
public void jump( long position) { ... }
public void execute() { ... }
}
class Memory {
public void load( long position, byte [] data) { }
}
class HardDrive {
public byte [] read( long lba, int size) { }
}
18/11/2009 UNICAM - p. 115/246
Facade. Esempio 2
Il front-end può essere realizzato da un facade.
/* Facade */
class Computer {
public void startComputer() {
cpu.freeze();
memory.load(BOOT_ADDRESS,hardDrive.read(BOOT_SECTOR ,SECTOR_SIZE));
cpu.jump(BOOT_ADDRESS);
cpu.execute();
}
}
18/11/2009 UNICAM - p. 116/246
Facade. Esempio 3
Il client usa il front-end facade per attivare tutti i sottosistemi.
/* Client */
class You {
public static void main(String[] args) {
Computer facade = new Computer();
facade.startComputer();
}
}
18/11/2009 UNICAM - p. 117/246
Flyweight
Il flyweight pattern permette di separare la parte variable di una classe dallaparte che può essere riutilizzata, in modo tale da poter avere quest’ultimacondivisa fra istanze differenti della parte variabile.
Un flyweight è un oggetto condiviso che può essere usato in contesti multiplisimultaneamente, ed è indistinguibile rispetto ad un’istanza non condivisa di unoggetto.
Il flyweight ha 2 stati: quello intrinseco , condiviso tra i diversi oggetti econtenuto all’interno del pattern e quello estrinseco , dipendente dal contesto,quindi non condiviso.Gli oggetti client sono responsabili del passaggio dello stato estrinseco alflyweight quando questi lo necessita.
18/11/2009 UNICAM - p. 118/246
Flyweight, scopo
Utilizzare in modo efficiente le operazioni.
Utilizzare meccanismi di condivisione per utilizzare efficientemente oggetti agrana fine.
18/11/2009 UNICAM - p. 119/246
Flyweight, applicabilità
Il flyweight andrebbe usato soltanto se tutte le seguenti condizioni risultasserovere:▲ L’applicazione utilizza un largo numero di oggetti;▲ I costi di memorizzazione sono elevati a causa della grande quantità di
oggetti;▲ La maggior parte dello stato dell’oggetto può essere resa estrinseca;▲ Diversi gruppi di oggetti possono essere sostituiti da pochi oggetti condivisi
una volta rimosso lo stato estrinseco;▲ L’applicazione non è dipendente dall’identità dell’oggetto;▲ Quando esistono due distinti stati che vengono variati con frequenza diversa;▲ Quando è possibile delegare ad un oggetto l’invocazione di operazioni
frequenti.
18/11/2009 UNICAM - p. 120/246
Flyweight, conseguenze
I flyweight introducono costi a runtime associati alla ricerca, al trasferimento, eal calcolo dello stato estrinseco, specialmente se questo è formalmenteimmagazzinato nello stato instrinseco. Tali costi sono ripagati dal risparmio dispazio, funzione dei seguenti fattori:▲ Riduzione del numero di istanze in conseguenza della condivisione;▲ Dimensione dello stato intrinseco di ogni oggetto;▲ Possibilità di calcolare lo stato estrinseco piuttosto che immagazzinarlo.
Ulteriori caratteristiche:▲ Modularità: è possibile aggiungere classi per modulare la frequenza di
chiamata;▲ L’invocazione di operazioni quando varia uno stato agisce solo sugli oggetti
effettivamente variati.
18/11/2009 UNICAM - p. 121/246
Flyweight: UML
FlyweightFactory
+getFlyweight(key)
Flyweight
+Operation(entrinsicState)
ConcreteFlyweight
intrinsicState
operation(extrinsicState)
UnsharedConcreteFlyweight
allState
operation(extrinsicState)
Client
Nota
-flyweights
18/11/2009 UNICAM - p. 122/246
Flyweight. Esempio 1
Per esempio, sia data una classe Punto che è molto usata, in cui i campi x e yvariano molto da un’istanza ad un’altra, mentre c’è poca variazione sul valoredel campo c di tipo Colore.
class Punto {
private int x, y;
private Color c; //parte poco variabile
Punto( int x, int y, Color c) {
this .x = x; this .y = y; this .c = c;
}
public String toString() { return b + "(" + x + ", " + y + ")";
}
}
18/11/2009 UNICAM - p. 123/246
Flyweight. Esempio 2
Si può creare un flyweight che contiene la parte che si può riutilizzare:
class ColorFlyWeight {
private Color c;
ColorFlyWeight(Color c) { this .c = c; }
public String toString( int x, int y) {
return c + "(" + x + ", " + y + ")";
}
}
class Color {
private int c;
Color( int c) { this .c = c; }
public String toString() {
return c == 0 ? "red" : (c == 1 ? "blue" : "green");
}}
18/11/2009 UNICAM - p. 124/246
Flyweight. Esempio 3
Invece la parte variabile resta nella classe Punto sostituendo il riferimento aColor con ColorFlyWeight.
class Punto {
private int x, y;
private ColorFlyWeight cF;
Punto( int x, int y, ColorFlyWeight c) {
this .x = x; this .y = y; cF = c;
}
public String toString() { return cF.toString(x, y); }
}
18/11/2009 UNICAM - p. 125/246
Flyweight. Esempio 4
class UsoDiPunto {
public static void main(String [] args) {
Punto [] map = new Punto[10000];
Color [] tr = { new Color(0), new Color(1), new Color(2)};
Random r = new Random();
for ( int i; i < map.length; i++)
map[i] = new Punto(r.next(i),r.next(i),tr[r.next(2)]);
for ( int i; i < map.length; i++)
System.out.println(map[i]);
}
}
18/11/2009 UNICAM - p. 126/246
Proxy
Un pattern Proxy è una classe che gioca il ruolo di un’altra classe RealSubject.Proxy ha la stessa interfaccia di RealSubject.Ci sono diverse situazioni in cui questo può essere interessante:▲ è costoso creare la classe RealSubject. Usando un proxy si può aspettare a
creare un oggetto di RealSubject fino a quando c’è veramente necessità;▲ non si può creare istantanemente la class RealSubject. In tale caso il Proxy
gioca il ruolo di RealSubject “simulando” il più possibile;▲ prima di accedere alla classe RealSubject bisogna verificare i diritti di
accesso.
Noto come: Surrogate.
18/11/2009 UNICAM - p. 127/246
Proxy, scopo
Creare un oggetto sosia che si comporti come l’oggetto reale, cioè fornire un“surrogato” di un altro oggetto in maniera di controllare l’accesso all’oggettostesso.
18/11/2009 UNICAM - p. 128/246
Proxy, applicabilità
▲ quando tenere in vita un oggetto costa molto;▲ quando l’esecuzione delle operazioni di un oggetto non può essere eseguita
immediatamente;▲ quando la creazione di un oggetto non può essere effettuata
immediatamente;▲ quando si necessita un riferimento ad un oggetto più versatile di un
puntatore.Alcuni esempi:
▲ Remote proxy: fornisce una rappresentazione locale di un oggettoappartenente ad uno spazio di indirizzamento diverso;
▲ Virtual proxy: crea oggetti “pesanti” on-demand;▲ Protection proxy: controlla l’accesso all’oggetto originale, il quale può avere
differenti diritti di accesso;▲ Smart reference: sostituzione di un semplice puntatore, che esegue
operazioni aggiuntive quando avviene l’accesso all’oggetto.
18/11/2009 UNICAM - p. 129/246
Proxy, conseguenze
▲ rinvia la creazione di un oggetto sino al primo utilizzo request();▲ è possibile aggiungere comportamenti non funzionali alle operazioni di
RealSubject per aumentare le prestazioni;▲ Proxy gestisce l’esecuzione delle operazioni in modo da ottimizzare il
funzionamento di RealSubject;▲ il Proxy inoltra le richieste al RealSubject quando opportuno, in base al tipo
di proxy realizzato;▲ Il remote proxy nasconde il fatto che un oggetto risieda in uno spazio di
indirizzamento diverso;▲ Il virual proxy introduce ottimizzazioni creando oggetti on-demand;▲ Il protection proxy e la smart reference introducono livelli di protezione
aggiuntivi.
18/11/2009 UNICAM - p. 130/246
Proxy: UML
ClientSubject
+request()
Proxy
+request()
RealSubject
+request()Nota
18/11/2009 UNICAM - p. 131/246
Proxy. Esempio 1
Per esempio, data una classe RealSubject che può fare un’azione:
class RealSubject extends Subject {
RealSubject() {} void request() { /*...*/ }
}
si può creare un proxy che crea RealSbject solamente quando c’è bisogno dieffettuare tale azione:
class Proxy extends Subject {
private Subject a;
Proxy() { a = null ; }
void request() {
if (a == null ) a = new RealSubject();
a.request();
}
}
18/11/2009 UNICAM - p. 132/246
Chain of Responsibility
Il design pattern Chain Of Responsibility disaccoppia l’oggetto che effettua unarichiesta dal destinatario dando a più oggetti la possibilità di rispondere. Glioggetti candidati a rispondere sono concatenati tra loro e passano la richiestalungo la catena sino a quando un oggetto la gestisce.
Il pattern Chain Of Responsibility si applica quando c’è una catena di oggettiche possono rispondere ad una richiesta. Questa catena è gerarchica. Larichiesta si muove lungo la catena per trovare l’oggetto più adatto perrispondere. Avere una struttura a catena permette che un oggetto non abbiabisogno di conoscere tutti gli elementi ma solamente il suo elementosuccessivo nella catena.
18/11/2009 UNICAM - p. 133/246
Chain of Responsibility, scopo
Evitare l’accoppiamento tra il mittente di una richiesta e il ricevente, dando apiù oggetti (collegati tra loro a catena) la possibilità di manipolare la richiesta.
18/11/2009 UNICAM - p. 134/246
Chain of Responsibility, applicabilità
Questo pattern andrebbe usato se:▲ Una richiesta può essere gestita da più di un oggetto, e il gestore non è noto
a priori;▲ Si desidera inviare una richiesta a diversi oggetti senza specificare
esplicitamente il ricevente;▲ L’insieme dei gestori della richiesta deve essere specificato dinamicamente.
18/11/2009 UNICAM - p. 135/246
Chain of Responsibility, conseguenze
▲ Isola le classi concrete;▲ Facilita la portabilità;▲ Aumenta la consistenza tra i prodotti;
▲ Per contro, inserire nuovi prodotti risulta complicato, in quanto implicacambiamenti all’Abstract Factory.
18/11/2009 UNICAM - p. 136/246
Chain if responsibility: UML
ClientHandler
+handlerRequest()
ConcreteHandler1
+handlerRequest()
ConcreteHandler2
+handlerRequest()
successor
18/11/2009 UNICAM - p. 137/246
Chain of responsibility. Esempio 1
Tipicamente questo pattern è implementato in Java usando un’interfaccia:
interface Chain {
void setUp(Chain chain);
void process(Object o);
boolean test(Object o);
void action(Object o);
}
Il primo metodo permette di settare l’elemento successivo. Il secondo metodo èil metodo che tratta una richiesta. I due metodi finali sono dei metodi diappoggio. Il primo permette di verificare se la richiesta possa essere trattatadall’oggetto e nel caso positivo il secondo definisce che cosa fare.
18/11/2009 UNICAM - p. 138/246
Chain of responsibility. Esempio 2
Gli elementi da processare sono:
class Element {
final static int NULL = 0, CHAR = 1, DOUBLE = 2;
private int type = NULL;
private Object e;
Element( char c) { type = CHAR; e = new Char(c); }
Element( double d) { type = DOUBLE; e = new Double(d); }
public getType() { return type; }
public Object getElement() { return element.clone(); }
}
18/11/2009 UNICAM - p. 139/246
Chain of responsibility. Esempio 3
Adesso tutti gli oggetti che possono comparire nella catena hanno bisogno diimplementare questa interfaccia. Per esempio, per una classe PrintCharabbiamo:
public class PrintChar implements Chain {
private Chain chain;
public void setUp(Chain c) { chain = c; }
public void process (Element o) {
if (test(o)) { action(o); }
else if (chain != null ) chain.process(o);
}
public boolean test(Element o) {
return o.getType() == Element.CHAR; }
public void action(Element o) {
System.out.print(o.getElement(); }
}
18/11/2009 UNICAM - p. 140/246
Chain of responsibility. Esempio 4
Per un’altra classe PrintDouble:
class PrintDouble implements Chain {
private Chain chain;
public void setUp(Chain c) { chain = c; }
public void process (Element o) {
if (test(o)) action(o);
else if (chain != null ) chain.process(o);
public boolean test(Element o) {
return o.getType() == Element.DOUBLE; }
public void action(Element o) {
System.out.print(o.getElement());
}
}
18/11/2009 UNICAM - p. 141/246
Chain of responsibility. Esempio 5
Tutte le classi che implementano l’interfaccia hanno lo stesso codice perprocesso. Questa è una situazione in cui il fatto di non avere lamulti-ereditarietà obbliga a duplicare codice.
class Chain {
public static void main(String [] args) {
PrintChar x = new PrintChar();
PrintDouble y = new PrintDouble();
x.setUp(y);
Element a = new Element(’a’), b = new Element(3.14);
x.process(a); x.process(b);
}
}
18/11/2009 UNICAM - p. 142/246
Command
Il design pattern Command permette (alla classe Client) di incapsulate unarichiesta, ossia un comando (Execute) da eseguire ed i suoi parametri (state),sotto forma di oggetto (ConcreteCommand) da usare per parametrizzare ilcomportamento di altri oggetti (Invoker) con diverse richieste (ossia con diversioggetti ConcreteCommand), code di richieste oppure log di richieste.Consente quindi di astrarre la chiamata di un metodo.
Noto come: Action, Transaction.
18/11/2009 UNICAM - p. 143/246
Command, scopo
Incapsulare una richiesta in un oggetto, in modo tale da parametrizzare i clientrispetto a richieste differenti, consentendo operazioni come accodamento,logging e undo.
18/11/2009 UNICAM - p. 144/246
Command, applicabilità
Il Command è utile per:▲ Parametrizzare oggetti rispetto ai comandi da eseguire (ciò nei linguaggi
procedurali spesso avviene per mezzo di una callback, ovvero una funzioneregistrata in un certo punto per poi essere richiamata successivamente);
▲ Specificare, accodare ed esegure richieste in tempi diversi;▲ Supportare l’undo: l’operazione di Execute può mantenere lo stato per
annullare il proprio effetto;▲ Supppotare il logging dei comandi in modo da consentire il re-do in caso di
crash del sistema;▲ Eseguire transazioni atomiche.
18/11/2009 UNICAM - p. 145/246
Command, conseguenze
▲ Il Command disaccoppia l’oggetto che invoca l’operazione da quello che laesegue;
▲ Un Command può essere manipolato o esteso come qualsiasi altro oggetto;▲ E’ possibile assemblare Commands creando dei comandi composti;▲ Aggiungere nuovi Command risulta semplice e non richiede modifiche alle
classi esistenti.
18/11/2009 UNICAM - p. 146/246
Command: UML
Client InvokerCommand
+execute()
ConcreteCommand
-state
+execute()
Receiver
+action()
receiver.action()
receiver
18/11/2009 UNICAM - p. 147/246
Command. Esempio 1
Si consideri un interruttore elettrico con due comandi: per accendere e perspegnere la luce.Un vantaggio di questa particolare implementazione del pattern di comando èche lo switch può essere utilizzato con qualsiasi dispositivo che prevede duecomandi per esempio accensione e spegnimento di un motore.Si noti come lo switch non debba conoscere direttamente o indirettamente idettagli sulla lampada.
18/11/2009 UNICAM - p. 148/246
Command. Esempio 2
In Java, un command prende la forma di un’intefaccia:
/* Invoker */
public class Switch {
private Command flipUpCommand;
private Command flipDownCommand;
public Switch(Command flipUpCmd, Command flipDownCmd) {
this .flipUpCommand = flipUpCmd;
this .flipDownCommand = flipDownCmd;
}
public void flipUp() { flipUpCommand.execute(); }
public void flipDown() { flipDownCommand.execute(); }
}
18/11/2009 UNICAM - p. 149/246
Command. Esempio 3
/* Receiver */
public class Light {
public Light() { }
public void turnOn() { System.out.println("The light is on"); }
public void turnOff() { System.out.println("The light is off"); }
}
18/11/2009 UNICAM - p. 150/246
Command. Esempio 4
/* Command */
public interface Command { void execute(); }
18/11/2009 UNICAM - p. 151/246
Command. Esempio 5
/* ConcreteCommand for Up */
public class FlipUpCommand implements Command {
private Light theLight;
public FlipUpCommand(Light light) { this .theLight=light; }
public void execute(){ theLight.turnOn(); }
}
/* ConcreteCommand for Down */
public class FlipDownCommand implement Command {
private Light theLight;
public FlipDownCommand(Light light) { this .theLight=light; }
public void execute() { theLight.turnOff(); }
}
18/11/2009 UNICAM - p. 152/246
Command. Esempio 6
/* Client */
public class PressSwitch {
public static void main(String[] args) {
Light lamp = new Light();
Command switchUp = new FlipUpComman(lamp);
Command switchDown = new FlipDownComman(lamp);
Switch s = new Switch(switchUp,switchDown);
try {
if (args[0].equalsIgnoreCase("ON")) s.flipUp();
else if (args[0].equalsIgnoreCase("OFF")) s.flipDown();
else
System.out.println("ON or OFF is required.");
} catch (Exception e){
System.out.println("Arguments required."); }
}
}
18/11/2009 UNICAM - p. 153/246
Interpreter
Il design pattern Interpreter include la capacità di interpretare elementi di unlinguaggio in un programma perché definisce una rappresentazione per lagrammatica del linguaggio a beneficio di un interprete che usa larappresentazione per interpretare frasi prodotte in quel linguaggio.
Esso si applica nella situazione in cui è utile avere un piccolo linguaggio dicomandi o di macro. Nei casi semplici si può scrivere il parser e l’interpretedirettamente usando classe comme StringTokenizer. Nei casi più elaborati siusa un toolkit dedicato (ad esempio ANTLR).
18/11/2009 UNICAM - p. 154/246
Interpreter, scopo
Dato un linguaggio, definire una rappresentazione per la grammatica di talelinguaggio e un interprete del linguaggio stesso.
18/11/2009 UNICAM - p. 155/246
Interpreter, applicabilità
L’Interpreter è applicabile quando c’è un linguaggio da interpretare, i cuistatements possono essere rappresentati da nodi dell’albero sintattico.Il pattern si rivela efficace se:▲ La grammatica è semplice (altrimenti la gerarchia di classi diventa
complessa e difficile da gestire);▲ L’efficienza non è essenziale (interpreti basati su espressioni regolari sono
più efficienti se realizzati mediante macchine a stati);
18/11/2009 UNICAM - p. 156/246
Interpreter, conseguenze
▲ E’ semplice modificare ed estendere la grammatica;▲ Implementare una grammatica risulta, allo stesso modo, abbastanza
agevole;▲ Tuttavia, è difficile gestire grammatiche complesse (che implicano gerarchie
complesse di classi);▲ E’ possibile aggiungere nuovi modi di interpretare la medesima espressione,
definendo nuove operazioni nelle classi relative alle espressioni stesse.
18/11/2009 UNICAM - p. 157/246
Interpreter: UML
Context
ClientAbstractExpression
+interpret(Context)
TerminalExpression
+interpret(Context)
NonterminalExpression
+interpret(Context)
18/11/2009 UNICAM - p. 158/246
Interpreter. Esempio 1
Si vuole realizzare una calcolatore per la valutazioni di espressioni in notazionepolacca inversa. La grammatica delle espressioni è la seguente:
expression ::= plus | minus | variableplus ::= expression expression ’+’minus ::= expression expression ’-’variable ::= ’a’ | ’b’ | ’c’ | ... | ’z’
Le espressioni prodotte con la precedente grammatica sono ad esempio:
a b +a b c + -a b + c a - -
18/11/2009 UNICAM - p. 159/246
Interpreter. Esempio 2
/* AbstractExpression */
import java.util. * ;
interface Expression {
public int interpret(HashMap variables);
}
18/11/2009 UNICAM - p. 160/246
Interpreter. Esempio 3
/* TerminalExpression */
class Number implements Expression {
private int number;
public Number( int number) { this .number = number; }
public int interpret(HashMap variables) { return number; }
}
class Variable implements Expression {
private String name;
public Variable(String name) { this .name = name; }
public int interpret(HashMap variables) {
return variables.get(name).intValue();
}
}
18/11/2009 UNICAM - p. 161/246
Interpreter. Esempio 3
/* NonterminalExpression */
class Plus implements Expression {
Expression leftOperand;
Expression rightOperand;
public Plus(Expression left, Expression right) {
leftOperand = left; rightOperand = right;
}
public int interpret(HashMap variables) {
return leftOperand.interpret(variables) +
rightOperand.interpret(variables);
}
}
18/11/2009 UNICAM - p. 162/246
Interpreter. Esempio 4
/* NonterminalExpression */
class Minus implements Expression {
Expression leftOperand;
Expression rightOperand;
public Minus(Expression left, Expression right) {
leftOperand = left; rightOperand = right;
}
public int interpret(HashMap variables) {
return leftOperand.interpret(variables) -
rightOperand.interpret(variables);
}
}
18/11/2009 UNICAM - p. 163/246
Interpreter. Esempio 5
/*Client */
class Evaluator {
private Expression tree;
public Evaluator(String exp) {
Stack stack = new Stack();
for (String token : exp.split(" ")) {
if (token.equals("+"))
stack.push( new Plus(stack.pop(), stack.pop()));
else if (token.equals("-"))
stack.push( new Minus(stack.pop(), stack.pop()));
else stack.push( new Variable(token));
}
tree = stack.pop();
}
public int evaluate(HashMap context) {
return tree.interpret(context);
}
}
18/11/2009 UNICAM - p. 164/246
Interpreter. Esempio 6
Infine si vuole valutare l’espressione "w x z - +" con w = 5, x = 10, and z = 42.
public class InterpreterExample {
public static void main(String[] args) {
String expression = "w x z - +";
Evaluator sentence = new Evaluator(expression);
HashMap variables = new HashMap(); // Context
variables.put("w", new Integer(5));
variables.put("x", new Integer(10));
variables.put("z", new Integer(42));
int result = sentence.evaluate(variables);
System.out.println(result);
}
}
18/11/2009 UNICAM - p. 165/246
Iterator
Il design pattern Iterator (ConcreteIterator) accede in modo sequenziale glielementi di un oggetto aggregato ossia di una collezione (ConcreteAggregate).Esso permette di scandire tutti gli elementi di una struttura senza conoscerel’esatta implementazione della struttura.
Noto come: Cursor.
18/11/2009 UNICAM - p. 166/246
Iterator, scopo
Fornire un meccanismo per accedere ad elementi di un oggetto aggregato inmaniera sequenziale, nascondendone la rappresentazione.
18/11/2009 UNICAM - p. 167/246
Iterator, applicabilità
L’Iterator è utilizzato per:▲ Accedere al contenuto di oggetti aggregati senza esporne la
rappresentazione interna;▲ Supportare modi di attraversamento multipli;▲ Fornire un’interfaccia multipla per attraversare diverse strutture (mediante il
polimorfismo degli iterator).
18/11/2009 UNICAM - p. 168/246
Iterator, conseguenze
▲ E’ possibile supportare diverse politiche di attraversamento;▲ Gli Iterator semplificano l’interfaccia dell’oggetto aggregato;▲ E’ possibile eseguire contemporaneamente più di un attraversamento sullo
stesso oggetto aggregato.
18/11/2009 UNICAM - p. 169/246
Iterator. Esempio 1
In Java è rappresentato dall’interfaccia Iterator.
public interface Iterator {
boolean hasNext();
Object next();
void remove();
}
18/11/2009 UNICAM - p. 170/246
Iterator. Esempio 2
Per esempio, un metodo che itera su un parametro di tipo Enumeration checontiene degli elementi di tipo A si scrive come
void process(Iterator enum) {
while (enum.hasNext()) {
A a = (A) enum.next();
//...
}
}
Ogni struttura dati in Java (Vector, Hashtable, ...) che estende Containerimplementa Iterator che permette di ottenere la scansione dei suoi elementi.
18/11/2009 UNICAM - p. 171/246
Iterator. Esempio 3
Si può anche creare un proprio Iterator. Per esempio, si può creare una classeche trasforma un array in una enumearazione:
import java.util.Iterator;
import java.util.NoSuchElementException;
class ArrayEnumeration implements Iterator {
private int index; private Object[] array;
ArrayEnumeration(Object[] a) { array = a; index = 0; }
public Object next() {
if (array.length <= index)
throw new NoSuchElementException();
return array[index++];
}
public boolean hasNext() { return (index < array.length); }
public void remove() {}
}
18/11/2009 UNICAM - p. 172/246
Iterator. Esempio 4
Si può anche modificare gli iteratori. Per esempio, data l’interfaccia chepermette di selezionare gli oggetti validi
interface Filter { boolean valid(Object o); }
18/11/2009 UNICAM - p. 173/246
Iterator. Esempio 5
si può trasformare un’enumerazione di oggetti in un’enumerazione di oggettivalidi:
class FilteredEnumeration implements Enumeration {
private Enumeration enum;
private Filter filter;
private Object element;
private boolean flag;
FilteredEnumeration(Enumeration e, Filter f) {
enum = e; filter = f; element = null ; flag = false ;
}
18/11/2009 UNICAM - p. 174/246
Iterator. Esempio 6
public Object nextElement() {
if (flag && filter.valid(element)) {
flag = false ; return element;
}
while ( true ) {
Object res = enum.nextElement();
if (filter.valid(res)) return res;
}
}
public boolean hasMoreElements() {
while ( true ) {
if (!(enum.hasMoreElements())) return false ;
element = enum.nextElement();
if (filter.valid(element)) {
flag = true ; return true ;
}}}
18/11/2009 UNICAM - p. 175/246
Mediator
Il design pattern Mediator è utile quando si desidera implementare unafunzionalità componendo il comportamento di diversi oggetti.Esso si applica in un gruppo di oggetti che interagiscono fra di loro. In tal casosi possono concentrare in un oggetto tutte le richieste di interazione. Cosìfacendo gli oggetti del gruppo devono solamente conoscere il mediatore perinteragire fra di loro.
18/11/2009 UNICAM - p. 176/246
Mediator, scopo
Definire un oggetto che incapsula il modo in cui diversi oggetti interagiscono traloro, evitando che tali oggetti possano referenziarsi a vicenda in manieraesplicita, e consentendo di cambiare il meccanismo di interazione in manieraagevole.
18/11/2009 UNICAM - p. 177/246
Mediator, applicabilità
Il Mediator è utilizzabile se:▲ Un insieme di oggetti comunicano in maniera ben definita (ma complessa) e
le interdipendenze risultando difficili da comprendere;▲ Il riuso di un oggetto è difficoltoso in quanto questo comunica con molti
oggetti;▲ Un behavior distribuito tra molte classi è customizzabile senza effettuare
molto subclassing.
18/11/2009 UNICAM - p. 178/246
Mediator, conseguenze
▲ Subclassing limitato;▲ Disaccoppiamento tra oggetti;▲ Semplificazione del protocollo di comunicazione tra oggetti;▲ Astrae come gli oggetti cooperano;▲ Centralizza il controllo.
18/11/2009 UNICAM - p. 179/246
Mediator: UML
Mediator Collaugue
ConcreteMediator ConcreteColleague1 ConcreteColleague2
18/11/2009 UNICAM - p. 180/246
Mediator. Esempio 1
Per esempio, consideriamo un gruppo di 3 oggetti, uno di tipo A:
class A {
private B b;
private C c;
void doIt() {
b.reset();
c.doIt();
}
void print(String s) {
//....
}
}
18/11/2009 UNICAM - p. 181/246
Mediator. Esempio 2
uno di tipo B:
class B {
private A a;
void reset() {
//....
}
void print(String s) {
a.print(s);
}
}
18/11/2009 UNICAM - p. 182/246
Mediator. Esempio 3
e uno di tipo C:
class C {
private A a;
void doIt() {
//....
}
void print(String s) {
a.print(s);
}
18/11/2009 UNICAM - p. 183/246
Mediator. Esempio 4
Applicando il pattern di mediazione, si crea un oggetto che conosce i treoggetti:
class AMediator {
private A a;
private B b;
private C c;
void doIt() {
b.reset();
c.doIt();
}
void print(String s) { a.print(s); }
}
18/11/2009 UNICAM - p. 184/246
Mediator. Esempio 5
Le class A, B e C si modificano di conseguenza:
class A {
private AMediator mediator;
void doIt() { mediator.doIt(); }
void print(String s) { }
}
class B {
private AMediator mediator;
void reset() { }
void print(String s) { mediator.print(s); }
}
class C {
private AMediator mediator;
void doIt() { }
void print(String s) { mediator.print(s); }
}
18/11/2009 UNICAM - p. 185/246
Memento
Il design pattern Memento senza violazioni della incapsulazione cattura lo statointerno di un oggetto (Originator) portandolo all’esterno (sottoforma diMemento) cosicchè quello stato possa essere ripristinato in seguito.Esso si applica quando c’è bisogno di conservare lo stato di un oggetto perdare la possibilità di recuperare questo stato più avanti.
Noto come: Token.
18/11/2009 UNICAM - p. 186/246
Memento, scopo
Senza violare l’incapsulamento, cattura ed esternalizza lo stato interno di unoggetto in modo che possa essere ripristinato in futuro.
18/11/2009 UNICAM - p. 187/246
Memento, applicabilità
Il Memento può essere usato se:▲ Occorre salvare lo stato di un oggetto (o una parte di esso) in modo da
poterlo ripristinare successivamente;▲ Un’interfaccia diretta per ottenere tale stato potrebbe esporre dettagli
implementativi (venendo meno così al concetto di incapsulamento).
18/11/2009 UNICAM - p. 188/246
Memento, conseguenze
▲ Preservare l’incapsulamento;▲ Semplificare l’Originator;▲ Utilizzare un Memento potrebbe tuttavia essere costoso (se copiare l’intero
stato dovesse risultare dispendioso; è possibile applicare un discorso diincrementalità, operando in maniera differenziale);
▲ In alcuni linguaggi è difficile limitare al solo Originator l’accesso al Memento;▲ Il costo del Memento non è noto al caretaker (che quindi non conosce la
dimensione dello stato immagazzinato).
18/11/2009 UNICAM - p. 189/246
Memento: UML
Originator
-state
+setMemento(in Memento)
+createMemeonto()
Memento
-state
+getState()
+setState()
Caretaker
return new Memento(state) state=m.getState()
18/11/2009 UNICAM - p. 190/246
Memento. Esempio 1
class Originator {
private String state;
public void set(String state) {
System.out.println("Setting state to " + state);
this .state = state;
}
public Object saveToMemento() {
System.out.println("Saving to Memento.");
return new Memento(state);
}
public void restoreFromMemento(Object m) {
if (m instanceof Memento) {
Memento memento = (Memento) m;
state = memento.getSavedState();
System.out.println("Restoring from Memento: "+state);
}}}
18/11/2009 UNICAM - p. 191/246
Memento. Esempio 2
private static class Memento {
private String state;
public Memento(String stateToSave) { state = stateToSave; }
public String getSavedState() { return state; }
}
18/11/2009 UNICAM - p. 192/246
Memento. Esempio 3
import java.util. * ;
class Caretaker {
private List savedStates = new ArrayList();
public void addMemento(Object m) { savedStates.add(m); }
public Object getMemento( int index) {
return savedStates.get(index);
}
}
18/11/2009 UNICAM - p. 193/246
Memento. Esempio 4
class MementoExample {
public static void main(String[] args) {
Caretaker caretaker = new Caretaker()
Originator originator = new Originator();
originator.set("State1");
originator.set("State2");
caretaker.addMemento(originator.saveToMemento());
originator.set("State3");
caretaker.addMemento(originator.saveToMemento());
originator.set("State4");
originator.restoreFromMemento(caretaker.getMemento( 1));
}
}
18/11/2009 UNICAM - p. 194/246
Memento. Esempio 5
Quando occorre, si può produrre lo stato dell’oggetto memorizzato chiamandoil metodo reset.
18/11/2009 UNICAM - p. 195/246
Observer
Il design pattern Observer viene in aiuto quando un oggetto (ConcreteSubject)vuole notificare il suo cambio di stato (subjectState) a un gruppo di oggetti (iConcreteObserver) a lui dipendenti.Esso si applica quando ci sono diversi oggetti interessati alle modifiche di unoggetto. Un’applicazione tipica di questo pattern è nella visualizzazione di unoggetto.Usare il pattern di osservazione permette di separare l’oggetto che èvisualizzato dalla visualizzazione.
Noto come: Dependant, Publish-Subscribe.
18/11/2009 UNICAM - p. 196/246
Observer, scopo
Definire una relazione uno-a-molti tra oggetti, in modo che quando un oggettocambia stato tutti gli ascoltatori collegati sono notificati ed aggiornati.
18/11/2009 UNICAM - p. 197/246
Observer, applicabilità
L’Observer andrebbe usato quando:▲ L’astrazione è composta da due aspetti, una dipendente dall’altra e si
desidera incapsulare le due astrazioni in oggetti separati;▲ Il cambiamento di un oggetto richiede il cambiamento di altri, senza sapere
quali;▲ Non si conosce a priori il numero degli oggetti dipendenti;▲ Un oggetto deve notificare ad altri un cambiamento senza conoscere la
struttura degli oggetti dipendenti.
18/11/2009 UNICAM - p. 198/246
Observer, conseguenze
▲ Accoppiamento astratto tra Subject e Observer: un Subject sa che ha unalista di Observer, conformi ad un’interfaccia astratta (AbstractObserver), manon è a conoscenza delle classi concrete degli stessi;
▲ Supporto alla comunicazione di tipo broadcast;▲ Aggiornamenti inattesi: se le interdipendenze non sono ben formate,
possono verificarsi aggiornamenti a cascata indesiderati;
In sintesi:▲ Maggiore modularità: Subject e Observer possono cambiare;▲ Maggiore elasticità: posso definire e aggiungere diversi Observer;▲ Maggiore flessibilità: posso agganciare diversi Observer ognuno dei quali
può implementare una differente vista.
18/11/2009 UNICAM - p. 199/246
Observer: UML
Subject
+attach(observer:Observer)
+detach(observer:Observer)
+notify()
Observer
+update()
ConcreteSubject ConcreteObserver
0..*
-observers
18/11/2009 UNICAM - p. 200/246
Observer. Esempio 1
Per esempio, si cunsideri il gioco della roulette:
class Roulette {
private int numero;
Random tavolo = new Random();
final private NUMERI = 36;
int getNumero() { return numero; }
void lancio() { numero = nextInt(NUMERI); }
//...
}
18/11/2009 UNICAM - p. 201/246
Observer. Esempio 2
Per permettere di osservare le modifiche, ovvero il risultato dei lanci, si creaprima un’interfaccia che rappresenta gli osservatori:
interface Observer { void update(); }
18/11/2009 UNICAM - p. 202/246
Observer. Esempio 3
Dopo si può modificare la classe Subject per tenere conto dei suoi osservatori:
class Roulette implement Subject{
private int numero;
private Vector observers;
Random tavolo = new Random();
final private NUMERI = 36;
public int getNumero() { return numero; }
void lancio() {
numero = nextInt(NUMERI);
for (Enumeration e = observers.elements();
e.hasMoreElements(); )
((Observer)e.nextElements()).update();
}
public void attach(Observer o) { observers.add(o); }
}
18/11/2009 UNICAM - p. 203/246
Observer. Esempio 4
Un osservatore ha bisogno di registrarsi con il metodo attach() e dopo vieneavvertito con il metodo update() quando il valore è stato cambiato. Perl’esempio della roulette vi possono essere diversi osservatori. Ad esempiol’osservatore del pari:
class Pari implements Observer {
private int pari = 0;
Pari(Subject s) { s.attach( this ); }
void update() { if (s.getNumero() % 2 == 0) pari++;
}
18/11/2009 UNICAM - p. 204/246
Observer. Esempio 5
L’osservatore del rosso:
class Rosso implements Observer {
private int rosso = 0;
static private int [] rossi = {2,4,6,8,10,11,13,15,17,20,
22,24,26,28,29,31,33,35};
Rosso(Subject s) { s.attach( this ); }
void update() { if (in(s.getNumero()) rosso++; }
private boolean in( int n) {
for ( int i = 0; i < rossi.length(); i++)
if (rossi[i]==n) return true ;
return false ;
}
}
18/11/2009 UNICAM - p. 205/246
State
Il design pattern State cambia il comportamento di un oggetto (il risultatodell’operazione Request dell’oggetto Context) al cambiare dello stato(ConcreteStateA, ConcreteStateB, ...).Esso si applica quando c’è un oggetto che cambia stato. Invece di avere unmetodo che effettua dei test per sapere quale codice usare, si usano gli oggetti.
Noto come: Objects o States.
18/11/2009 UNICAM - p. 206/246
State, scopo
Descrivere il comportamento di un sistema mediante la composizione di azioniintraprese in un numero finito di stati. In ciascuno stato vengono intrapresesingole azioni. La somma delle azioni determina il comportamentocomplessivo del sistema.Consentire ad un oggetto di modificare il proprio comportamento quando il suostato interno cambia.
18/11/2009 UNICAM - p. 207/246
State, applicabilità
Utilizzare lo State nei seguenti casi:▲ Quando l’astrazione permette di descrivere il sistema come un automa a
stati finiti;▲ Quando il cambiamento del comportamento del sistema in un determinato
stato non comporta la modifica del comportamento di altri stati;▲ Quando non si conosce a priori il numero degli stati;▲ Quando le azioni che vengono intrapese in uno stato non dipendono dalle
azioni intraprese in altri stati;▲ Il comportamento di un oggetto dipende dal suo stato, e deve modificare il
proprio comportamento a run-time in base alla variazione dello stato;▲ Le operazioni sono implementate mediante largo uso di statement
condizionali dipendenti dai valori assunti dalle variabili di stato: lo State poneciascun branch in una classe separata.
18/11/2009 UNICAM - p. 208/246
State, conseguenze
▲ Partiziona i comportamenti state-specific;▲ Rende esplicite le transizioni tra stati;▲ Protegge il Context dalla possibilità di passare in stati inconsistenti;▲ Possibilità di condividere (es. mediante un Flyweight) degli State objects;
In sistesi:▲ Maggiore modularità: il comportamento dell’intero sistema è la composizione
dei comportamente dei singoli stati;▲ Maggiore elasticità: si possono aggiungere nuovi stati senza modificare
pesantemente il codice;▲ Maggiore flessibilità: è possibile modificare il comportamento di uno stato
senza alterare il sistema.
18/11/2009 UNICAM - p. 209/246
State: UML
Context
+request()
State
+handle()
ConcreteStateA
+handle()
ConcreteStateB
+handle()
states.handle()
0..*
-states
18/11/2009 UNICAM - p. 210/246
State
▲ L’applicazione del pattern State comporta a creazione di unConcreteContext e di un insime di ConcreteState
▲ ConcreteContext descrive il comportamento del sistema. Mantiene unriferimento allo stato concreto corrente
▲ ConcreteContext invoca mediante il riferimento allo stato correntel’esecuzione delle operazioni associate allo stato mediante l’interfaccia dioperazione standard "handle()".
▲ L’operazione "handle()" può avere come argomento "handle(Context c)" cosìche i singoli stati possono indicare lo stato succssivo al Context.
18/11/2009 UNICAM - p. 211/246
State, esempio 1
Si vuole modellare il comportamento di un orogogio digitale con due pulsanti:MODE e CHANGE. Il punsante MODE permette di selezionare le modalità:"visualizzazione normale", "modifica dell’ora", "modifica dei minuti". Il secondopulsante CHANGE permette di accendere il display se la modalità difunzionamento è normale, di incrementare le ore o i minuti se nelle modalità"modifica dell’ora" o "modifica dei minuti".
18/11/2009 UNICAM - p. 212/246
State, esempio 2
NormalDispaly
UpdatingHours
UpdatingMinutes
CHANGE/Display() CHANGE/IncHours()
CHANGE/IncMinutes()
MODE
MODEMODE
18/11/2009 UNICAM - p. 213/246
State, esempio 3
Il codice per Clock è il seguente:
class Clock extends Context {
private State currentState = new Display();
private String button;
public String getButton() { return button; }
public void setState(State s) { currentState = s; }
public void request() { /*...*/ }
}
dove gli stati Display, Hours e Minutes susseguono, applicando il pattern distate, si crea prima la classe degli stati
18/11/2009 UNICAM - p. 214/246
State. Esempio 2
class NormalDisplay extends State {
void handle(Context c) {
if (c.getButton() == CHANGE) {
Display(); c.setState( new NormalDisplay());
}
if (c.getButton() == MODE) { c.setState( new UpdatingHours());
}
}
class UpdatingHours extends State {
void handle(Context c) {
if (c.getButton() == CHANGE) {
IncHours(); c.setState( new UpdatingHours());
}
if (c.getButton() == MODE) { c.setState( new UpdatingMinutes());
}
}
18/11/2009 UNICAM - p. 215/246
State. Esempio 3
class UpdatingMinutes extends State {
void handle(Context c) {
if (c.getButton() == CHANGE) {
IncMinutes(); c.setState( new UpdatingMinutes());
}
if (c.getButton() == MODE) { c.setState( new NormalDisplay());
}
}
18/11/2009 UNICAM - p. 216/246
State. Esempio 4
Il metodo handle(State s) di ogni stato esegue le operazioni di suacompetenza quindi termina determinando quale nuovo stato deve assumere ilsistema e lo comunica al contesto mediante il metodo setState(State s) .Quindi quando il controllo ritorna a request() , questi reinvocherà il metodohandle(State s) dello stato attuale.
public void request() {
while ( true ) {
button = getButton();
currentState.handle( this );
}
}
In request() viene effettuata la lettura del tasto.
18/11/2009 UNICAM - p. 217/246
Strategy
Il design pattern Strategy incapsula una famiglia di algoritm in una famiglia(Strategy) di classi (ConcreteStrategyA, ConcreteStrategyB, ...) rendendoliintercambiabili in base alla situazione (Context). La scelta di quale algoritmodella famiglia utilizzare è determinata dal contesto in cui viene impiegato(Context), poi l’utilizzo di un algoritmo piuttosto che l’altro è del tuttotrasparente.Esso è molto simile a quello di stato. Il pattern di strategia permette di averedei comportamenti a scelta. Si applica in situazioni dove esistono diversestrategie per fare una cosa ma l’utente (o l’oggetto) ne sceglie una solamente.
Noto come: Policy.
18/11/2009 UNICAM - p. 218/246
Strategy, scopo
Definire una famiglia di algoritmi, incapsularli e renderli intercambiabili inmaniera trasparente rispetto all’uso da parte del client.
18/11/2009 UNICAM - p. 219/246
Strategy, applicabilità
Lo Strategy è utile se:▲ Classi collegate differiscono solo per il loro comportamento (es. algoritmi di
compressione che minimizzano lo spazio/minimizzano il tempo);▲ Gli algoritmi utilizzano dati non a conoscenza del client;▲ Una classe definisce differenti comportamenti, espressi come statement
condizionali multipli nelle operazioni (il problema si risolve in maniera simileal caso del pattern State).
18/11/2009 UNICAM - p. 220/246
Strategy, conseguenze
▲ Creazione di famiglie di algoritmi collegati;▲ E’ un’alternativa al subclassing;▲ Eliminazione degli statement condizionali;▲ Possibilità di scelta a run-time di una implementazione;▲ Il client deve essere a conoscenza delle differenti strategie prima di
selezionarne una;▲ Possibilità di overhead di comunicazione tra Strategy e Context;▲ Il numero di oggetti cresce.
18/11/2009 UNICAM - p. 221/246
Strategy: UML
Context
+contexInterface()
Strategy
+algorithmInterface()
ConcreteStrategyA
+algorithmInterface()
ConcreteStrategyB
+algorithmInterface()
ConcreteStrategyC
+algorithmInterface()
strategy
18/11/2009 UNICAM - p. 222/246
Strategy. Esempio 1
Per esempio, per implementare una classe A che stampa un oggetto in duemodi diversi (postscript o pdf) si può scrivere come segue:
abstract class Strategy {
abstract void print(Object o);
}
class PS extends Strategy {
void print(Object o) { ... }
}
class PDF extends Strategy {
void print(Object o) { ... }
}
18/11/2009 UNICAM - p. 223/246
Strategy. Esempio 2
class Context {
private Strategy strategy;
void setStrategy(Strategy s) { strategy = s; }
void print(Object o, String s) { // imposta il ConcreteStrategy
if ("ps".equals(s)) {
setStrategy( new PS());
} else if ("pdf".equals(s)) {
setStrategy( new PDF());
}
print(o);
}
void print(Object o) { strategy.print(o); }
}
18/11/2009 UNICAM - p. 224/246
Template
Il design pattern Template Method definisce lo scheletro di un algoritmo (nelmetodo TemplateMethod di AbstractClass) e delega ad una classe derivata(ConcreteClass) la possibilità di ridefinire alcuni passi (PrimitiveOperation1,PrimitiveOperation2, ...) dell’algoritmo (nel metodo TemplateMethod) senzabisogno di modificare l’intero algoritmo o la sua struttura.Esso permette di scrivere metodi generici. Tale pattern è la base dellaprogrammazione orientata agli oggetti. Per istanziare questo metodo si usal’ereditarietà dando un’implementazione alle operazioni sulle quali si appoggiail metodo generico.
18/11/2009 UNICAM - p. 225/246
Template, scopo
Definire lo scheletro di un algoritmo in un’operazione, delegando alcunisubsteps alle sottoclassi, le quali possono modificarli senza impattare lastruttura dell’algoritmo.
18/11/2009 UNICAM - p. 226/246
Template, applicabilità
Il template method andrebbe usato:▲ Per implementare parti invarianti di un algoritmo e lasciare alle sottoclassi
l’implementazione dei comportamenti variabili;▲ Quando risulta opportuno “clusterizzare” comportamenti comuni tra
sottoclassi per evitare duplicazione di codice;▲ Per controllare estensioni di sottoclassi.
18/11/2009 UNICAM - p. 227/246
Template, conseguenze
Il Template Method è fondamentale per effettuare riuso, particolarmente nellarealizzazione di librerie di classi, in quanto costituisce un metodo perfattorizzare comportamenti comuni.
18/11/2009 UNICAM - p. 228/246
Template: UML
...primitiveOperation1()...primitiveOperation2()...
AbstractClass
+templateMethod()
+primitiveOperation1()
+primitiveOperation2()
ConcreteClass
+primitiveOperation1()
+primitiveOperation2()
18/11/2009 UNICAM - p. 229/246
Template. Esempio 1
Per esempio, consideriamo un metodo generico per disegnare un quadrato. Sesi dispone di un metodo per andare avanti e di un metodo per girare a destra,un metodo generico può essere definito andando avanti, girando, avanti,girando, avanti. Tale metodo generico si scrive in Java nel modo seguente:
abstract class A {
abstract void goForth(); // primitiveOperation1
abstract void goRight(); // primitivaOperation2
final void doSquare() { // templateMethod
goForth(); goRight();
goForth(); goRight();
goForth(); goRight();
goForth();
}
}
18/11/2009 UNICAM - p. 230/246
Template. Esempio 2
Adesso nelle class che derivano da A ogni volta che si daràun’implementazione di goForth e goRight si erediterà un metodo per fare deiquadrati. La cosa importante è che se il codice di doSquare è stato scrittoprima dell’implementazione di goForth e goRight si userà comunque taleimplementazione nell’esecuzione.
18/11/2009 UNICAM - p. 231/246
Visitor
Il design pattern Visitor si applica quando si ha un insieme di oggetti e c’èbisogno di "visitare" questi oggetti per effettuare la computazione. L’idea delpattern di visita è che questa visita si può fare in modo esterno all’oggetto,occorre solamente che l’oggetto preveda di essere visitato.
18/11/2009 UNICAM - p. 232/246
Visitor, scopo
Rappresentare un’operazione da eseguire sugli elementi di una struttura. IlVisitor consente di definire nuove operazioni senza modificare le classi deglielementi su cui operare.Esso rende indifferenti gli oggetti da computare rispetto alla computazione.
18/11/2009 UNICAM - p. 233/246
Visitor, applicabilità
Il visitor andrebbe usato quando:▲ Una struttura di oggetti contiene diverse classi con diverse interfacce, è si
desidera eseguire le operazioni in base alle classi concrete;▲ Occorre eseguire sulla struttura diverse operazioni distinte e scorrelate, e si
desidera evitare di complicare l’interfaccia delle classi della struttura,aggiungendo le diverse operazioni;
▲ E’ possibile separare l’algoritmo da applicare agli oggetti dagli oggetti stessi;▲ Si vogliono adottare diversi schemi di elaborazione sugli stessi oggetti;▲ Gli oggetti a cui si applica la computazione possono subire alterazioni dello
stato senza che ciò comporti alterazioni dell’agoritmo.▲ IMPORTANTE: le classi della struttura cambiano raramente, ma occorre
spesso invece definire nuove operazioni. Modifiche alla strutturarichiederebbero la ridefinizione dell’interfaccia di tutti i visitor;
18/11/2009 UNICAM - p. 234/246
Visitor, conseguenze
▲ L’aggiunta di nuove operazioni è agevole;▲ Le operazioni correlate sono messe assieme, quelle scorrelate sono
separate;▲ E’ difficile aggiungere nuove classi di ConcreteElement;▲ I visitor consentono di accumulare stato;▲ Il visitor assume che il ConcreteElement abbia un’interfaccia in grado di
consentirgli di svolgere il proprio compito. Ciò forza la pubblicazione dialcune operazioni del ConcreteElement, in contrasto con i meccanismi diincapsulamento;
▲ Indipendenza degli algoritmi dagli oggetti da computare;▲ Applicazione di diversi algoritmi a diversi oggetti senza conseguenze nel
client.
18/11/2009 UNICAM - p. 235/246
Visitor: UML
visitor.visitConcreteElement(this) visitor.visitConcreteElement(this)
Client
Visitor
+VisitConcreteElementA(ConcreteElementA)
+VisitConcreteElementB(ConcreteElementB)
ConcreteVisitor1
+VisitConcreteElementA(ConcreteElementA)
+VisitConcreteElementB(ConcreteElementB)
ConcreteVisitor2
+VisitConcreteElementA(ConcreteElementA)
+VisitConcreteElementB(ConcreteElementB)
ObjectStructure
Element
+Accept(Visitor)
ConcreteElementA
+accept(Visitor)
+operationA()
ConcreteElementB
+accept(Visitor)
+operationB()
18/11/2009 UNICAM - p. 236/246
Visitor. Esempio 1
Tale pattern è molto utile per esempio nel caso di un oggetto composito. Peresempio, consideriamo gli alberi binari. Abbiamo una classe astratta
abstract class Tree { }
e due sottoclassi
18/11/2009 UNICAM - p. 237/246
Visitor. Esempio 2
class Node extends Tree {
private String name;
private Tree left;
private Tree right;
Node(String n, Tree l, Tree r) { right = r; left = l; name = n; }
public String getName() { return name; }
public Tree getLeft() { return left; }
public Tree getRight() { return right; }
}
18/11/2009 UNICAM - p. 238/246
Visitor. Esempio 3
e
class Leaf extends Tree {
private int value;
Leaf ( int v) { value = v; }
public int getValue() { return value; }
}
Adesso un visitatore per questa classe sarà un oggetto che ha due metodi, unoper ogni sottoclasse:
abstract class Visitor {
abstract void visit(Node node);
abstract void visit(Leaf leaf);
}
18/11/2009 UNICAM - p. 239/246
Visitor. Esempio 4
La prima cosa da fare è di permettere a ogni componente di un albero diaccettare il visitatore. Si fa con il metodo accept:
abstract class Tree {
abstract void accept(Visitor v);
}
18/11/2009 UNICAM - p. 240/246
Visitor. Esempio 5
class Node extends Tree {
private String name;
private Tree left;
private Tree right;
Node(String n, Tree l, Tree r) {
right = r; left = l; name = n;
}
public String getName() { return name; }
public Tree getLeft() { return left; }
public Tree getRight() { return right; }
public void accept(Visitor v) { v.visit( this ); }
}
18/11/2009 UNICAM - p. 241/246
Visitor. Esempio 6
class Leaf extends Tree {
private int v;
Leaf ( int value) { value = v; }
public int getValue() { return value; }
public void accept(Visitor v) { v.visit( this ); }
}
18/11/2009 UNICAM - p. 242/246
Visitor. Esempio 7
Adesso per scrivere un visitatore si può estendere la classe Visitor. Peresempio, possiamo scrivere un visitatore che stampa gli elementi dell’albero inmodo prefisso:
class PrefixVisitor extends Visitor {
void visit(Node node) {
System.out.println(node.getName());
node.getLeft().accept( this );
node.getRight().accept( this );
}
void visit(Leaf leaf) {
System.out.println(leaf.getValue());
}
}
18/11/2009 UNICAM - p. 243/246
Visitor. Esempio 8
un altro in modo postfisso:
class PostfixVisitor extends Visitor {
void visit(Node node) {
node.getLeft().accept( this );
node.getRight().accept( this );
System.out.println(node.getName());
}
void visit(Leaf leaf) {
System.out.println(leaf.getValue());
}
}
18/11/2009 UNICAM - p. 244/246
Visitor. Esempio 9
Dato l’albero
Tree t = new Node("a", new Node("b", new Leaf(1),
new Leaf(2)), new Leaf(3));
t.accept( new PrefixVisitor());
produceab123
18/11/2009 UNICAM - p. 245/246
Visitor. Esempio 10
Mentre
t.accept( new PostfixVisitor());
produce12b3a
18/11/2009 UNICAM - p. 246/246
Visitor. Esempio 11
Usando il pattern di visita si possono aggiungere funzionalità ad una class fuoridella sua definizione. Il fatto che il meccanismo di visita sia così complicato(accept chiama visit che richiama accept) è una conseguenza del fatto chel’overloading è risolto staticamente.