Visualizzazione post con etichetta pattern. Mostra tutti i post
Visualizzazione post con etichetta pattern. Mostra tutti i post

Anatre in formazione

Completiamo il discorso sul pattern Strategy secondo quanto si trova nel primo capitolo di Head First - Design Patterns, descrivendo la gerarchia di anatre e poi le gerarchie di comportamenti associati.

La classe base delle anatre é Duck, una classe astratta che ha due dati membro che sono poi le due strategie dell'anatra: il modo di volare e il modo di emettere un verso.

Notiamo che, per essere utilizzabile, un anatra reale dovrà definire le due strategie, in modo che possano essere coerentemente invocati i metodi fly() e quack() su di essa. Il metodo swim é comune a tutte le implementazioni, e quindi viene definito in Duck.

package hf.strategy;

public abstract class Duck {
protected FlyBehavior flyBehavior;
protected QuackBehavior quackBehavior;

public Duck() {
}

public void setFlyBehavior (FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}

public void setQuackBehavior(QuackBehavior quackBehavior) {
this.quackBehavior = quackBehavior;
}

public void fly() {
flyBehavior.fly();
}

public void quack() {
quackBehavior.quack();
}

public void swim() {
System.out.println("All ducks float, even decoys!");
}
}


La definizione di un'anatra reale richiederà la specificazione delle strategie adottate. Vediamo ad esempio la classe MallardDuck:


package hf.strategy;

public class MallardDuck extends Duck {
public MallardDuck() {
quackBehavior = new Quack();
flyBehavior = new FlyWithWings();
}
}

Strategie

Restano ora da definire le strategie. Al top della gerarchia di una strategia ci sarà un'interfaccia, che dichiara il metodo che viene richiesto. Qui vediamo la strategia di volo:

package hf.strategy;

public interface FlyBehavior {
public void fly();
}

E qui la strategia di starnazzamento:

package hf.strategy;

public interface QuackBehavior {
public void quack();
}


Una classe reale utilizzerà una implementazione coerente con i suoi scopi delle strategie richieste. MallardDuck richiedeva queste:

package hf.strategy;

public class Quack implements QuackBehavior {
public void quack() {
System.out.println("Quack");
}
}

// ---
package hf.strategy;

public class FlyWithWings implements FlyBehavior {
public void fly() {
System.out.println("I'm flying!!");
}
}

La strategia dell'anatra

Nel primo capitolo di Head First - Design Patterns si tratta il pattern Strategy a partire dalla progettazione di una gerarchia di classi che descrivono il comportamento di diversi tipi di anatre.

Il problema é quello di dare alla gerarchia una robustezza tale da permetterle di reggere agevolemente a cambiamenti futuri delle specifiche di progetto.

Il pattern Strategy definisce una famiglia di algoritmi, li incapsula, e li rende intercambiabili. Strategy fa un modo che l'algoritmo cambi indipendentemente dal client che lo usa.

Nel corso del capitolo vengono citati tre importanti principi, che riporto qui a seguire.

Il fatto che di costante nello sviluppo software sia solo il cambiamento conduce al seguente principio di progettazione: identifica gli aspetti dell'applicazione che cambiano e separali da quelli che non mutano.

Un altro importante principio: programma verso un interfaccia e non verso una implementazione. Questo permette una maggior astrazione del codice e quindi un miglior adattamento del codice al cambiamento.

Un terzo principio: preferisci la composizione all'ereditarietà. Permette molta più flessibilità, dato che permette di cambiare il comportamento di una classe a runtime.

Detto questo, vediamo le specifiche del nostro progetto.

Vogliamo gestire una gerarchia di anatre, in modo da poter gestire classi che rappresentano sia animali veri e propri che oggetti come un anatra di gomma o un anatra da richiamo.

L'idea é che alcuni algoritmi che potrebbero sembrare scontati nella nostra gerarchia (il volare dell'anatra, nel nostro esempio) in realtà non lo sono.

Nell'implementazione originale, ogni nostra anatra poteva volare, emettere un verso (quack!) e nuotare. Creando nuove classi derivate, abbiamo cozzato contro una variazione della progettazione originale: esistono anatre che non emettono versi e anatre che non volano.

Abbiamo quindi rifattorizzato la classe di base per riflettere questo cambiamento: l'anatra generica si appoggia su classi esterne che implementano la strategia della specifica anatra.

Avremo quindi una gerarchia di classi basate in Duck, la classe astratta da cui tutte le nostre anatre derivano, e una gerarchia per ogni strategia che Duck utilizza.

Nel nostro caso abbiamo una interfaccia FlyBehavior per la strategia di volo e QuackBehavior per la strategia di starnazzamento.

Nei post seguenti vedremo il codice per implementare questa soluzione.

Head First - Design Patterns

Abbandono la rilettura di Design Patterns, Elements of Reusable Object-Oriented Software di Erich Gamma et al., comunemente noti come the Gang of Four, ottimo libro edito dalla Addison-Wesley, per passare alla prima lettura di Head First - Design Patterns dei Freeman, edito da O'Reilly che, a quanto ho visto sfogliandolo rapidamente, sembra una riscrittura aggiornata ai tempi (é del 2004) del classico dei GoF. Il linguaggio utilizzato per implementare gli esempi é Java.

I toni solo leggeri, ma il contenuto mi pare ci sia e sia molto ben trattato. Unico appunto che mi sento di fare é che é rivolto a un pubblico molto americano, e quindi una parte dei giochi di parole e dei riferimenti extra informatici restano abbastanza oscuri al lettore europeo.

Una buona citazione da questo libro penso sia questa: "Un bravo progettista software pensa a come creare design flessibili che siano manutenibili e che possano adattarsi al cambiamento".

Command (e Null Object)

Pattern descritto in Design Pattern.

Lo scopo del pattern Command (aka Action, Transaction) é incapsulare una richiesta in un oggetto, in questo modo si parametrizzano clienti con differenti richieste e si supportano operazioni reversibili.

Si usa Command:
  • per parametrizzare oggetti con una azione da eseguire. Nota che l'uso di Command in questo modo ricorda quello del meccanismo di callback, dove una funzione viene registrata per essere eseguita in seguito;
  • per specificare, mettere in coda, ed eseguire comandi successivamente;
  • per supportare l'undo di una operazione;
  • per permettere il log di cambiamenti in modo da poterli riapplicare in caso di bisogno. Aggiungendo i metodi load() e store() all'interfaccia si può tenere una traccia persistente dei cambiamenti;
  • per implementare le transazioni.
Esempio

Nel nostro mondo fantasy la potente corporazione dei maghi ha l'esclusiva sull'uso della magia. Ma c'é un modo per i non maghi di eseguire alcuni sortilegi: comprando una bacchetta magica su cui un mago ha registrato alcune magie ben delimitate.

Ad esempio si può chiedere a un mago di registrare l'incantamento che fa aprire una porta ben specificata e quello che la fa chiudere. E' prevista anche la possibilità di annullare la magia, nel caso la si sia invocata per sbaglio.

Alla base della nostra implementazione c'é l'interfaccia Command, che viene definita in questo modo:

package gof.command;

public interface Command {
public void execute();
public void undo();
}

E la classe Door, che rappresenta una porta, con i metodi che la fanno aprire e chiudere:

package gof.command;

public class Door {
private final String name;
private boolean open = false;

public Door(String name) {
this.name = name;
}

public String getName() {
return name;
}

public boolean isOpen() {
return open;
}

public void open() {
open = true;
}

public void close() {
open = false;
}

@Override
public String toString() {
return getName() + " is " + (isOpen() ? "open" : "closed");
}
}

La magia che ci permette di aprire una porta é descritta dalla classe OpenDoorSpell:

package gof.command;

public class OpenDoorSpell implements Command {
Door door;

public OpenDoorSpell(Door door) {
this.door = door;
}

public void execute() {
System.out.println(door.getName() + ": Open Sesame!");
door.open();
}

public void undo() {
System.out.println(door.getName() + ": Undo Open Sesame!");
door.close();
}
}

L'incantamento che ci permette di chiuderla é questo:

package gof.command;

public class CloseDoorSpell implements Command {
Door door;

public CloseDoorSpell(Door door) {
this.door = door;
}

public void execute() {
System.out.println(door.getName() + ": Close Sesame!");
door.close();
}

public void undo() {
System.out.println(door.getName() + ": Undo Close Sesame!");
door.open();
}
}

Vale la pena di crearsi anche una classe che rappresenta la non-magia. E' spesso utile avere un oggetto che non fa nulla, quando sappiamo a prescindere cosa fare nelle situazioni in cui non c'é un oggetto di quel tipo a disposizione. Nel caso delle magie é semplice: non succede nulla. L'uso di un oggetto che ha in sé la conoscenza di cosa fare in caso non vi sia alcun oggetto disponibile invece di lasciare all'utilizzatore il compito di decidere cosa fare in questa situazione viene descritto come il pattern del Null Object.

Null Object viene spesso implementato come un Singleton, come facciamo anche noi in questo caso:

package gof.command;

public class NullSpell implements Command {
private static NullSpell instance = new NullSpell();

public static NullSpell getInstance() {
return instance;
}

private NullSpell() {}

public void execute() {
System.out.println(this.getClass().getSimpleName() + ": no spell");
}

public void undo() {
System.out.println(this.getClass().getSimpleName() + ": nothing happen");
}
}

La nostra semplice bacchetta magica sarà un oggetto di questa classe:

package gof.command;

public class EasyWand {
public enum Type { ONE, TWO };
private static final int NR_SPELLS = 2;
private Command[] spells = new Command[NR_SPELLS];

public EasyWand() {
Command nothing = new NullSpell();

for(int i = 0; i < NR_SPELLS; ++i) {
spells[i] = nothing;
}
}

public void setSpell(Type type, Command spell) {
spells[type.ordinal()] = spell;
}

public void spell(Type type) {
spells[type.ordinal()].execute();
}
}

Notiamo che inizialmente le magie contenute nella bacchetta sono il Null Object. Come da aspettative, la bacchetta non fa nulla, bisogna esplicitamente associare magie alla bacchetta.

Test

Per prima cosa costruiamo una bacchetta secondo la richiesta del nostro utente, che vuole poter aprire e chiudere una porta di cui ci passa un riferimento:

private static void easyWand(Door door) {
Command open = new OpenDoorSpell(door);
Command close = new CloseDoorSpell(door);
EasyWand wand = new EasyWand();

System.out.println("A new wand, no spell on it");
wand.spell(EasyWand.Type.ONE);

wand.setSpell(EasyWand.Type.ONE, open);
wand.setSpell(EasyWand.Type.TWO, close);

System.out.println("The wand is ready to be used");
// ...

L'output di questa prima parte dovrebbe essere questo:

A new wand, no spell on it
NullSpell: no spell
The wand is ready to be used

Passiamo dunque al test vero e proprio della bacchetta. Apriamo, chiudiamo la porta, e poi cerchiamo due volte di fare l'undo. Siccome nella nostra implementazione é possibile fare l'undo solo dell'ultima operazione, il secondo undo non avrà successo:

// ...
System.out.println("Opening " + door.getName());
wand.spell(EasyWand.Type.ONE);
System.out.println(door.toString());

System.out.println("Closing " + door.getName());
wand.spell(EasyWand.Type.TWO);
System.out.println(door.toString());

System.out.println("Undoing Closing " + door.getName());
wand.undo();
System.out.println(door.toString());

wand.undo();
System.out.println(door.toString());
}

L'output atteso é il seguente:

Opening Front Door
Front Door: Open Sesame!
Front Door is open
Closing Front Door
Front Door: Close Sesame!
Front Door is closed
Undoing Closing Front Door
Front Door: Undo Close Sesame!
Front Door is open
NullSpell: nothing happen
Front Door is open

Proxy

Pattern descritto in Design Pattern.

Lo scopo del pattern Proxy (aka Surrogate) é quello di fornire un segnaposto per un altro oggetto per controllarne l'accesso.

Ci sono svariati utilizzi per il proxy:
  • Il proxy remoto offre una rappresentazione locale di un oggetto che in realtà risiede altrove. In Java un proxy di questo tipo lo si trova nell'implementazione del modello sottostante alla RMI (Remote Method Invocation) dove il proxy é chiamato stub e l'oggetto remoto a cui si riferisce skeleton.
  • Il proxy virtuale permette di ritardare la creazione di un oggetto costoso in termini di risorse.
  • Il proxy protettivo permette di diversificare l'accesso ad un oggetto a seconda delle diverse situazioni.
  • La smart reference sostituisce il semplice puntatore con una struttura che permette operazioni aggiuntive. Tipico esempio é lo smart pointer comunemente usato in C++.
Esempio

Nel nostro mondo fantasy é prevista l'esistenza di un oracolo a cui é possibile sottoporre delle domande che otterranno, dopo adeguata meditazione, una risposta.

L'interfaccia che descrive l'oracolo é molto semplice:

package gof.proxy;

public interface Oracle {
public String getAnswer(String question);
}

L'implementazione che diamo per l'oracolo reale é decisamente semplicistica, ma l'idea che c'é dietro é che si tratti di codice molto complesso che richiede grandi risorse e un notevole tempo per essere eseguito:

package gof.proxy;

public class RealOracle implements Oracle {
private String answer = "I don't know";

public RealOracle() {
try{Thread.sleep(500);} catch(InterruptedException ie) {}
System.out.println("[Oracle] I'm ready.");
}

public String getAnswer(String question) {
System.out.println("[Oracle] You asked me: " + question);
try{Thread.sleep(2000);} catch(InterruptedException ie) {}

return answer;
}
}

Per verificare il funzionamento del nostro oracolo scriviamo questa funzione:

private String direct(String question) {
System.out.println("My question is: " + question);

Oracle oracle = new RealOracle();
return oracle.getAnswer(question);
}

Che dovrebbe dare come output:

My question is: What is the sense of life?
[Oracle] I'm ready.
[Oracle] You asked me: What is the sense of life?
The oracle's answer is: I don't know

A parte la delusione per la risposta che otteniamo dall'oracolo, il fatto fastidioso é che restiamo appesi in attesa del risultato senza poter far altro.
Questo ci fa pensare di usare un proxy al nostro oracolo, che permetta al cliente di fare altro mentre l'oracolo pensa:

package gof.proxy;

public class OracleProxy implements Oracle {
private OracleThread oracle = null;

public String getAnswer(String question) {
if(oracle == null) {
oracle = new OracleThread(question);
oracle.start();
}

String answer = oracle.getAnswer();

if(answer == null) {
System.out.println("Oracle needs more time to answer.");
}
else {
try { oracle.join(); } catch (InterruptedException ex) {}
}
return answer;
}
}

Il nostro oracolo é stato messo in un altro thread, il proxy lo interroga e, se ha elaborato una risposta, la passa al chiamante, altrimenti ritorna un null dopo aver stampato un messaggio che avverte che l'oracolo é ancora perso nelle sue elucubrazioni.

Questo é il codice della classe d'appoggio per l'oracolo multithreading:

package gof.proxy;

public class OracleThread extends Thread {
private RealOracle oracle = new RealOracle();
private String answer = null;
private String question;

public OracleThread(String question) {
this.question = question;
}

public String getAnswer() {
return answer;
}

@Override
public void run() {
answer = oracle.getAnswer(question);
}
}

La funzione che ci permette di testare il proxy é questa:

private String proxy(String question) {
System.out.println("My question is: " + question);

Oracle oracle = new OracleProxy();
String answer = null;
do {
try { Thread.sleep(500); } catch(InterruptedException ie) {}
System.out.println("I have time to do other stuff...");
} while((answer = oracle.getAnswer(question)) == null );

return answer;
}

Che dà questo risultato:

My question is: What is the sense of life?
I have time to do other stuff...
[Oracle] I'm ready.
Oracle needs more time to answer.
[Oracle] You asked me: What is the sense of life?
I have time to do other stuff...
Oracle needs more time to answer.
I have time to do other stuff...
Oracle needs more time to answer.
I have time to do other stuff...
Oracle needs more time to answer.
I have time to do other stuff...
The oracle's answer is: I don't know

OK. Il risultato finale é lo stesso, però almeno il cliente dell'oracolo ha avuto la possibilità di impiegare più utilmente il suo tempo nell'attesa della risposta.

Iterator

Pattern descritto in Design Pattern.

Lo scopo del pattern Iterator é quello di fornire una di accesso sequenziale agli elementi di un oggetto aggregato senza esporre la rappresentazione interna.

Esempio

L'interfaccia Iterator disponibile in Java, java.util.Iterator, é leggermente diversa da quella proposta dalla Gang of Four, in particolare prevede anche un metodo per rimuovere l'elemento corrente dalla collezione sottostante. Usiamo comunque quella nel nostro esempio.

Il problema del nostro mondo fantasy che utilizziamo per il nostro esempio é quello di implementare una classe che descrive un tipo peculiare di personaggi che posseggono mezza dozzina di mostri al loro servizio. Questa implementazione prevede che i mostri siano esattamente sei, vengono creati assieme al personaggio ed esistono solo in quanto appartenenti al personaggio stesso.

Per questo motivo li implementiamo con un array e li rendiamo disponibili all'utente della classe per mezzo di un iteratore.

Questo é il codice relativo alla nostra classe Monster, in una implementazione minimale:

package gof.iterator;

public class Monster {
private final int number;
private boolean active = true;

public Monster(int number) {
this.number = number;
}

public int getNumber() {
return number;
}

public boolean isActive() {
return active;
}

public void setActive(boolean active) {
this.active = active;
}
}

La classe che rappresenta il padrone dei mostri viene in questo modo modo:

package gof.iterator;

import java.util.Iterator;

public class MonsterMaster {
private final String name;
private Monster[] monsters = new Monster[6];

public MonsterMaster(String name) {
this.name = name;

for(int i = 0; i < 6; ++i)
monsters[i] = new Monster(i + 1);
}

public String getName() {
return name;
}

public Iterator getIterator() {
return new MonsterIterator(monsters);
}
}

E questa é l'implementazione per il nostro iteratore, che usa come collezione sottostante un array di mostri, come richiesto:

package gof.iterator;

import java.util.Iterator;

public class MonsterIterator implements Iterator {
private Monster[] monsters;
private int current = 0;

public MonsterIterator(Monster[] monsters) {
this.monsters = monsters;
}

public boolean hasNext() {
if(current == monsters.length)
return false;
return true;
}

public Object next() {
if(current == monsters.length)
return null;

return monsters[current++];
}

public void remove() {
monsters[current].setActive(false);
}
}

Verifichiamo la nostra implementazione con questa funzione:

public static void main(String[] args) {
MonsterMaster master = new MonsterMaster("MoMa");

Iterator it = master.getIterator();

System.out.println("List of monsters for " + master.getName());
while(it.hasNext()) {
Monster m = it.next();

System.out.print(m.getNumber() + " ");
}
System.out.println();
}

Singleton

Pattern descritto in Design Pattern.

Lo scopo del pattern Singleton é quello di assicurare che una classe ha una sola istanza, e gli fornisce un punto di accesso globale.

Esempio

E' un pattern molto usato, in quanto accade molto spesso di dover garantire che esista un solo oggetto di una determinata classe. Un esempio per il nostro mondo fantasy potrebbe essere il genio della lampada, alla Alì Babà. Viene descritto da una classe specifica, dato che ha caratteristiche sue peculiari, e ne esiste uno solo.

E' anche un pattern a cui bisogna dedicare una certa attenzione perché tocca un punto delicato. O meglio, se il nostro singleton si trova ad essere eseguito in un singolo thread le cose sono piuttosto semplici, ma sorgono problemi se lavoriamo, come di solito accade in un ambiente "moderno", in multithreading.

Iniziamo a vedere il caso in cui ci sia un solo thread di esecuzione.

Singleton senza multithreading

Dobbiamo regolamentare la creazione del genio, per questo motivo dichiariamo il costruttore standard privato, impedendo di fatto la creazione di genii all'esterno della classe stessa.

Mettiamo quindi a disposizione un metodo statico, per consuetudine chiamato getInstance(), che crea l'unica instanza della classe, se non é ancora stata creata, e la ritorna al chiamante.

Oltre al meccanismo proprio per la gestione del singleton aggiungiamo alla classe una proprietà per tener traccia del fortunato che viene riconosciuto dal genio come suo capo, e metodi per il get e set del master.

La classe andrebbe estesa per gestire tre desideri e quindi resettare il master a null, ma tralasciamo, per il momento.

Il codice minimale é perciò questo:

package gof.singleton;

public class LampGenie {
private static LampGenie instance = null;

public static LampGenie getInstance() {
if(instance == null)
instance = new LampGenie();
return instance;
}

private Human master = null;

private LampGenie() {}

public Human getMaster() {
return master;
}

boolean setMaster(Human newMaster) {
if(master == null) {
System.out.println("Setting the master to " + newMaster.getName());
master = newMaster;
return true;
}
else {
System.out.println("I already have a master: " + master.getName());
return false;
}
}
}

Singleton in multithreading

Ma che succede alla getInstance() se il codice é eseguito contemporaneamente da più thread? In linea teorica possiamo avere grossi problemi e, come ci insegnano le Leggi di Murphy, possiamo star certi che nel momento meno opportuno qualcosa di sgradevole capiterà anche nella pratica.

Infatti se un thread trova che instance é correntemente pari a null, potrebbe ben essere, che un altro thread abbia appena fatto lo stesso controllo e gli cambi il valore di instance praticamente sotto i piedi, ottenendo così il risultato inaspettato che entrambi i thread finiscono per creare una nuova instance.

Abbiamo infatti due competitori per un unica risorsa il cui accesso, per non evitare questi pasticci, va regolamentato.

Ci sono diversi modi di superare il problema, in funzione del contesto in cui ci troviamo.

In questo caso abbiamo una risorsa, il genio della lampada, che costa poco creare, ed é normalmente usata nel corso della nostra applicazione (che storia sarebbe mai senza genio della lampada?), percui ci viene naturale prendere la decisione di lasciare che ci pensi la Java Virtual Machine a garantire che le cose vadano per il verso giusto. Chiamiamo il costruttore per il singleton al momendo della sua dichiarazione come oggetto statico. Ci penserà la JVM a chiamare il costruttore garantendo l'assenza di conflitti tra thread.

Se ci va bene questo approccio, faremo questi cambiamenti nella classe, che rinominiamo SafeLampGenie:

public class SafeLampGenie {
private static SafeLampGenie instance = new SafeLampGenie();

public static SafeLampGenie getInstance() {
return instance;
}
...

Altrimenti dovremmo riscrivere il metodo getInstance implementando noi la sincronizzazione, ovvero il modo che Java ci mette a disposizione per gestire il problema della concorrenza.

Ne parliamo in un altro post, magari.

Per completare l'esempio manca la definizione della classe Human, che riduciamo ai minimi termini, come segue:

package gof.singleton;

public class Human {
private final String name;

public Human(String name) {
this.name = name;
}

public String getName() {
return name;
}
}


E quindi un funzione main che ci permetta di verificare il funzionamento di quanto abbiamo scritto:

public static void main(String[] args) {
Human tom = new Human("Tom");
Human bob = new Human("Bob");

if(SafeLampGenie.getInstance().getMaster() == null) {
System.out.println("Genie is looking for a master");
}

if(SafeLampGenie.getInstance().setMaster(tom))
System.out.println(tom.getName() + ": I'm the Genie Master!");
if(SafeLampGenie.getInstance().setMaster(bob) == false)
System.out.println(bob.getName() + ": Damn, I'm late!");
}

Template Method

Pattern descritto in Design Pattern.

Lo scopo del pattern Template Method é quello di definire la struttura di un algoritmo, lasciando che alcuni suoi passi vengano definiti dalle sottoclassi. In questo modo le sottoclassi possono ridefinire alcuni passi dell'algoritmo senza cambiarne la struttura.

Partecipanti
  • AbstractClass, definisce come astratte le operazioni primitive che sono implementate alle classi derivate e che definiscono i singoli passi dell'algoritmo; implementa il template method che definisce la struttura dell'algoritmo e che usa le funzioni che definiscono i singoli passi, come definiti nella classe astratta o nelle classi derivate;
  • ConcreteClass, implementa i singoli passi come richiesto dalla versione dell'algoritmo.
Esempio

Nella nostra applicazione fantasy, i personaggi possono andare in appositi negozi per comprare oggetti di utilità più o meno comune. La struttura dell'acquisto é sempre la stessa per ogni negozio: il negoziante valuta se il cliente é di suo gradimento, poi verifica che il prodotto selezionato sia adeguato per il cliente, ci si accorda sul prezzo e solo come ultimo passo viene dato al cliente l'ambito prodotto.

Il problema é che ogni negozio ha una diversa valutazione del cliente (alcuni vendono solo a maghi, ad esempio), ha un diverso magazzino, e un diverso listino. Insomma, vogliamo avere un algoritmo di vendita che abbia una struttura rigida, che deve essere eseguita come stabilito da tutti i negozi, ma nel contempo vogliamo anche una notevole libertà implementativa in ogni passo dell'algoritmo.

Il pattern Template Method ci aiuta a realizzare proprio questo.

AbstractClass

In cima alla gerarchia di negozi c'é la classe astratta Shop, che mette a disposizione il metodo template che qui si chiama buy() e ritorna l'acquisto fatto (o null, in caso la trattativa non vada a buon termine).

In questo caso non abbiamo una implementazione di default per nessuno dei passi dell'algoritmo definito da buy(), perciò tutti i metodi che definiscono i passi intermedi sono astratti:

package gof.templateMethod;

public abstract class Shop {
protected final String name;

public Shop(String name) {
this.name = name;
}

public String getName() {
return name;
}

public Good buy(Character character, int item, int price) {
if(checkCharacter(character) == false) {
System.out.println(name + ": I won't sell you anything");
return null;
}

if(checkGood(character, item) == false) {
System.out.println(name + ": Sorry I can't sell you that");
return null;
}

if(checkPrice(character, item, price) == false) {
System.out.println(name + ": Price is high, but quality too!");
return null;
}

return makeGood();
}

abstract protected boolean checkCharacter(Character character);

abstract protected boolean checkGood(Character character, int item);

abstract protected boolean checkPrice(Character character, int item, int price);

abstract protected Good makeGood();
}

ConcreteClass

Definiamo un solo tipo negozio in questo esempio. Si tratta dell'armeria. Una regola fondamentale della gilda degli armaioli é che solo gli umani possono comprare qui, e quindi quello che farà la checkCharacter() é assicurarsi che il cliente sia umano.
Le armerie vendono prodotti che hanno un numero di codice, stabilito dal Grande Catalogo Universale, compreso tra 1001 e 2000. Controllo che viene compiuto in checkGood(). Per alcuni prodotti potrebbe essere necessario fare altri controlli aggiuntivi, dato che alcune armi piuttosto difficili da usare potrebbero essere vendute solo a personaggi con particolari caratteristiche, per questo motivo si passa a questo metodo anche il riferimento al cliente.
Anche l'implementazione del listino prezzi é correntemente molto debole, tutte le merci disponibili sono vendute a 5 (o più), ma é prevista la possibilità di avere prezzi diversi per ogni tipo di merce venduta, come ci si dovrebbe aspettare, e anche in funzione del cliente.
Del resto il catalogo é in realtà correntemente molto limitato, dato che la makeGood() in realtà ritorna sempre l'istanza di una semplice spada.

Ecco a seguire la classe che implementa le nostre armerie:

package gof.templateMethod;

public class Armory extends Shop {
public Armory(String name) {
super(name);
}

@Override
protected boolean checkCharacter(Character character) {
if(character instanceof Human)
return true;

return false;
}

@Override
protected boolean checkGood(Character character, int item) {
if(item > 1000 && item < 2000)
return true;
return false;
}

@Override
protected boolean checkPrice(Character character, int item, int price) {
if(price >= 5)
return true;
return false;
}

@Override
protected Good makeGood(int item) {
System.out.println(name + ": here is your Simple Sword!");
return new SimpleSword();
}
}

Classi al contorno

Resta da definire la gerarchia di classi dei clienti, di cui diamo una definizione minimale così:

public interface Character {
abstract public String getName();
}

class Human implements Character {
private final String name;

public Human(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

class Monster implements Character {
private final String name;

public Monster(String name) {
this.name = name;
}

public String getName() {
return name;
}
}

Ed ecco la gerarchia di oggetti commerciabili:

public interface Good {
public int getPrice();
}

public interface Weapon extends Good {
public int getOffensiveBonus();
public int getDefensiveBonus();
}

public class SimpleSword implements Weapon {
private int price = 10;
private int offensiveBonus = 12;
private int defensiveBonus = 6;

public int getPrice() {
return price;
}

public int getOffensiveBonus() {
return offensiveBonus;
}

public int getDefensiveBonus() {
return defensiveBonus;
}
}

Main

Resta solo da scrivere un piccolo test client:

public static void main(String[] args) {
Shop shop = new Armory("Kill Safe");

System.out.println("Tom wants a sword from " + shop.getName());

Character tom = new Monster("Tom");
Good good = shop.buy(tom, 1001, 5);
if(good == null) {
System.out.println("Tom goes somewhere else.");
}

System.out.println("Tom wants a something from " + shop.getName());
Character tim = new Human("Tim");
good = shop.buy(tim, 1, 100);

if(good == null) {
System.out.println("Tim looks for a sword");
}
good = shop.buy(tim, 1001, 4);

if(good == null) {
System.out.println("Tim increases his offer");
}
good = shop.buy(tim, 1001, 5);

if(good instanceof Weapon) {
Weapon w = (Weapon)good;

System.out.println("The offensive bonus is " + w.getOffensiveBonus());
}
}

Che produce questo output:

Tom wants a sword from Kill Safe
Kill Safe: I won't sell you anything
Tom goes somewhere else.
Tom wants a sword from Kill Safe
Kill Safe: Sorry I can't sell you that
Tim looks for something else
Kill Safe: Price is high, but quality too!
Tim increases his offer
Kill Safe: here is your Simple Sword!
The offensive bonus is 12

Strategy

Pattern descritto in Design Pattern.

Lo scopo del pattern Strategy (aka Policy) é quello di definire una famiglia di algoritmi, incapsularli e renderli intercambiabili. Con l'uso di Strategy é possibile rendere l'algoritomo variabile in modo indipendente dall'oggetto che lo usa.

Partecipanti
  • Strategy: dichiara una interfaccia comune a tutti gli algoritmi supportati.
  • ConcreteStrategy: implementa gli algoritmi usando l'interfaccia Strategy.
  • Context: configurato con un oggetto di tipo ConcreteStrategy, mantiene un riferimento all'interfaccia Strategy, può definire un interfaccia per l'accesso di Strategy ai suoi dati.

Esempio

Nella nostra applicazione fantasy i personaggi spesso combattono fra loro. Il nostro problema é che esistono diversi modi di combattere, alcuni brutti ceffi fanno ricorso quasi esclusivamente alla forza bruta, altri, che possono essere non meno pericolosi, integrano la loro forza, solitamente limitata, con l'uso della loro astuzia, altri ancora, sapienti maghi, se provocati utilizzano le loro ben allenate facoltà mentali, e solo marginalmente la propria forza e abilità per causare danni al loro oppositore. Insomma, ogni carattere ha la sua diversa strategia nel combattimento.

E questo non é tutto. Ne succedono di tutti i colori in quel mondo, può persino accadere che un valoroso guerriero decida di abbandonare l'uso delle armi e mettersi a praticare la magia. Ovvero, un carattere non ha una strategia di combattimento immutabile, al contrario questa può cambiare nel corso del tempo.

Contesto astratto

I nostri personaggi saranno umani, mostri, e chissà che altro. Modifichiamo quindi leggermente il pattern inserendo una classe astratta Character che ne sintetizza le qualità comuni e fornisce una referenza alla strategia di combattimento:

package gof.strategy;

public abstract class Character {
protected String name;
protected int force;
protected int intelligence;
protected int ability;

protected CombatStrategy combat;

public Character(String name, int force, int intelligence, int ability) {
this.name = name;
this.force = force;
this.intelligence = intelligence;
this.ability = ability;
}

public void setCombatStrategy(CombatStrategy combat) {
this.combat = combat;
}

public void fight() {
System.out.print("For fighting " + name + " uses the ");
System.out.println(combat.getClass().getSimpleName() + ": ");

combat.fight();
}
}

Il cuore del pattern é nel fatto che il modo di combattere é definito da una gerarchia di classi che ha origine nell'interfaccia CombatStrategy, e il Context usa una ConcreteStrategy non derivando da essa ma possedendo un'istanza della classe. In termini Object-Oriented: il Character non é, ma puttosto ha una CombatStrategy.
Questo ci permette di cambiare facilmente il modo di combattere del nostro personaggio, basta usare il metodo setCombatStrategy() che aggiorna la sua strategia di combattimento.
Quindi il metodo fight() semplicemente delega l'attività alla CombatStrategy corrente.

Contesto concreto

Gli umani entrano in gioco, nel nostro mondo fantasy, con una certa propensione alla violenza. Questo viene descritto dal costruttore della classe, che gli assegna come strategia di combattimento iniziale quella propria di guerriero brutale:

package gof.strategy;

public class Human extends Character {
public Human(String name, int force, int intelligence, int ability) {
super(name, force, intelligence, ability);

combat = new BrutalWarriorStrategy(this);
}
}

Strategia

Nella versione iniziale la nostra strategia é una interfaccia molto semplice, mettendo a disposizione solo il metodo fight(), che permette di combattere:

package gof.strategy;

public interface CombatStrategy {
public void fight();
}


Strategie concrete

Vediamo un paio di possibili strategie che implementano la nostra interfaccia, cominciando dal default per gli umani, il guerriero brutale:

package gof.strategy;

public class BrutalWarriorStrategy implements CombatStrategy {
private Character character;

public BrutalWarriorStrategy(Character character) {
this.character = character;
}

public void fight() {
System.out.println("90% force: " + character.force * 0.90);
System.out.println("5% ability: " + character.ability * 0.05);
System.out.println("5% intelligence: " + character.intelligence * 0.05);
System.out.println("---");
}
}

Quello che cambia, rispetto alla strategia del mago, che vediamo a seguire, e solo nel modo in cui vengono gestite le caratteristiche del carattere che sua la strategia:

package gof.strategy;

public class WizardStrategy implements CombatStrategy {
private Character character;

public WizardStrategy(Character character) {
this.character = character;
}

public void fight() {
System.out.println("10% force: " + character.force * 0.1);
System.out.println("20% ability: " + character.ability * 0.2);
System.out.println("70% intelligence: " + character.intelligence * 0.7);
System.out.println("---");
}
}

Main

Ci resta da scrivere un piccolo tester, che crea un umano, ne verifica il modo di combattere, gli cambia la strategia e vede come ciò impatta sul di lui:

public static void main(String[] args) {
Character tom = new Human("Tom", 50, 40, 30);

tom.fight();

CombatStrategy combat = new WizardStrategy(tom);
tom.setCombatStrategy(combat);

tom.fight();
}

Questo l'output che ci attendiamo:

For fighting Tom uses the BrutalWarriorStrategy:
90% force: 45.0
5% ability: 1.5
5% intelligence: 2.0
---
For fighting Tom uses the WizardStrategy:
10% force: 5.0
20% ability: 6.0
70% intelligence: 28.0
---

Observer

Pattern descritto in Design Pattern.

Lo scopo del pattern Observer (aka Publish/Subscribe) é quello di definire una dipendenza uno a molti tra oggetti in modo che quando lo stato dell'uno cambia, tutti i dipendenti ricevono una notifica del fatto.

Partecipanti
  • Subject: il soggetto osservato, sa chi lo osserva, non vi sono limiti teorici sul numero degli osservatori. Fornisce un'interfaccia per connettere e disconnettere gli osservatori.
  • Observer: definisce l'interfaccia dell'osservatore, mettendo a disposizione un metodo per notificare i cambiamenti del Subject.
  • ConcreteSubject: tiene traccia dello stato del soggetto
  • ConcreteObserver: mantiene un riferimento al Subject; implementa l'interfaccia Observer;
Esempio

In Java esiste una implementazione standard di questo pattern, nel package java.util é infatti definita la classe Observable e l'interfaccia Observer.

Notiamo che Observable é una classe, mentre il pattern prevederebbe l'uso di un'interfaccia. Questo, considerando inoltre le limitazioni di Java che non permette l'ereditarietà multipla, porta a dover sottostare a dei compromessi se si usa questa implementazione.

Nel nostro caso non abbiamo problemi particolari e quindi usiamo volentieri il codice che ci troviamo a disposizione.

Consideriamo nella nostra applicazione fantasy il caso di un personaggio che riesca con il suo carisma a raggruppare intorno a sé un manipolo di seguaci. Il seguace guadagna da questa relazione un bonus che lo renderà più forte in certe situazioni, ma paga questo con un certo assoggettamento ai voleri del master che, notificandolo ai seguaci, ottiene che questi lo combattano al posto suo.

Questo vale solo se il carisma del master é superiore al desiderio di indipendenza del seguace. Altrimenti il seguace non ci pensa due volte a lasciare il master per conto suo.

Master

Il master della situazione sarà un oggetto di tipo derivato da java.util.Observable, avrà un nome, e un carisma (rappresentato da un intero). Il nome del master non può cambiare nel corso della sua vita, percui avremo un getter ma non un setter per questa proprietà.

Il nostro master può fare fondamentalmente tre cose che hanno un influsso sui suoi seguaci:
cambiare il livello del proprio carisma: il seguace dovrà decidere se essere ancora legato al master in seguito a questo cambiamento;
cercare di aggiungere un seguace: se il seguace é d'accordo, gioirà della relazione, altrimenti annullerà immediatamente l'iscrizione;
attaccare un nemico: l'istanza sgradita al master viene passata ai seguaci, che lo attaccheranno al posto suo.

Vediamo dunque il codice per la classe Master risultante:

package gof.observer;

import java.util.Observable;
import java.util.Observer;

public class Master extends Observable {
private final String name;
private int charisma;

public Master(String name, int charisma) {
this.name = name;
this.charisma = charisma;
}

public String getName() {
return name;
}

public int getCharisma() {
return charisma;
}

public void setCharisma(int charisma) {
this.charisma = charisma;

System.out.println(name + ": \"My charisma has changed.\"");
setChanged();
notifyObservers();
System.out.println("---");
}

@Override
public synchronized void addObserver(Observer o) {
super.addObserver(o);
setChanged();
notifyObservers();
System.out.println("---");
}

public void attack(Warrior enemy) {
System.out.println(name + ": \"I have an enemy!\"");
setChanged();
notifyObservers(enemy);
System.out.println("---");
}
}

Warrior

In questa prima versione il master può raccogliere seguaci solo tra i Guerrieri, quindi abbiamo una classe Warrior che implementa l'interfaccia java.util.Observer, dichiarando inoltre un riferimento al master corrente per il guerriero (inizialmente sarà null, e il guerriero sarà libero), il suo livello di indipendenza (un intero) e il bonus che ottiene in cambio del suo legame al master (zero in caso di indipendenza)

Le tre attività peculiari del guerriero in quanto dipendente a un master sono quelle di elevare una lode al master, abbandonarlo (il che comporta annullare il proprio bonus), e combattere un guerriero segnalatogli dal master stesso. Sono tutti metodi privati, dato che verranno richiamati internamente alla classe stessa.

Il metodo chiave del pattern é update(), dichiarato nell'interfaccia Observer, e che implementiamo qui in questo modo: ci accertiamo che la richiesta stia arrivando da un Master, altrimenti la rifiutiamo (sdegnosamente, un guerriero accetta solo Master come master); se il guerriero non ha già un master, lo accetta, altrimenti declina l'offerta (un guerriero é fedele al proprio master, finchè ha un adeguato carisma); controlliamo il carisma del nostro nuovo master, se inferiore alle aspettative, lo abbandoniamo; aggiorniamo il nostro bonus, in funzione del carisma del master (un quarto del suo carisma, in questa versione); se il master non ci ha passato lavoro sporco da fare, eleviamo la nostra lode a lui; altrimenti controlliamo cosa ci ha passato, se si tratta di un guerriero lo combattiamo.

Ecco il codice per questa classe:

package gof.observer;

import java.util.Observable;
import java.util.Observer;

public class Warrior implements Observer {
Master master;
int indipendence;
int bonus = 0;

private String name;

public String getName() {
return name;
}

public Warrior(String name, int indipendence) {
this.indipendence = indipendence;
this.name = name;
}

public void update(Observable o, Object arg) {
if(!(o instanceof Master)) {
return;
}
if(master == null)
master = (Master)o;
else if(o != master) {
return;
}

if(master.getCharisma() < indipendence) {
master.deleteObserver(this);
leave();
return;
}

bonus = master.getCharisma() / 4;
if(arg == null) {
prise();
return;
}

if(arg instanceof Warrior) {
fight((Warrior)arg);
}
}

private void prise() {
System.out.println(name + ": \"" + master.getName() + " is cool.\"");
}

private void leave() {
System.out.println(name + ": \"I don't like " + master.getName() + ".\"");
master = null;
bonus = 0;
}

private void fight(Warrior w) {
System.out.println(name + ": \"" + w.getName() + " is my enemy!\"");
}
}

Main

Ci resta da scrivere un piccolo test client in cui creiamo un master, un paio di guerrieri, li associamo al master, creiamo un terzo guerriero che viene attaccato dal master, in seguito a questa disdicevole azione il master perde buona parte del suo carisma, il che lo porterà a perdere un membro del suo seguito, nonostante ciò il master itera il suo attacco contro il guerriero:

public static void main(String[] args) {
Master master = new Master("MasterPlaster", 12);

Warrior tom = new Warrior("Tom", 6);
Warrior bob = new Warrior("Bob", 8);

master.addObserver(bob);
master.addObserver(tom);

Warrior bill = new Warrior("Bill", 12);
master.attack(bill);

master.setCharisma(7);
master.attack(bill);
}

Questo l'output del nostro test client:

Bob: "MasterPlaster is cool."
---
Tom: "MasterPlaster is cool."
Bob: "MasterPlaster is cool."
---
MasterPlaster: "I have an enemy!"
Tom: "Bill is my enemy!"
Bob: "Bill is my enemy!"
---
MasterPlaster: "My charisma has changed."
Tom: "MasterPlaster is cool."
Bob: "I don't like MasterPlaster."
---
MasterPlaster: "I have an enemy!"
Tom: "Bill is my enemy!"

Factory Method

Pattern descritto in Design Pattern.

Lo scopo del pattern Factory Method (aka Costruttore Virtuale) é quello di definire un'interfaccia per la creazione di un oggetto, ma di lasciar decidere alle sottoclassi quale classe istanziare.

Partecipanti
  • Product: definisce l'interfaccia dell'oggetto creato dalla factory
  • ConcreteProduct: implementa l'interfaccia Product
  • Creator: dichiara il factory method
  • ConcreteCreator: ridefinisce il factory method
Esempio

Nel mondo fantasy che modelliamo per la nostra applicazione ci sono dei "cattivi" che creano mostri che manderanno a combattere contro i nostri eroi.
Ogni "cattivo" può generare, in questa prima implementazione, tre tipi di mostri di potenza crescente.

Creator

Il nostro "cattivo" é quindi il Creator del pattern, e rispetterà questa interfaccia:

public interface BadGuy {
enum MonsterType {SIMPLE, AVERAGE, STRONG}
public Monster createMonster(MonsterType type);
}


ConcreteCreator

Ogni cattivo che usiamo nel nostro sistema implementa l'interfaccia BadGuy, ad esempio, vediamone uno:

public class Zardoz implements BadGuy {

public Monster createMonster(MonsterType type) {

switch(type) {
case SIMPLE:
return new TinMan();
case AVERAGE:
return new StrawMan();
case STRONG:
default:
return new ShyLion();
}
}
}


Product

I mostri creati dai nostri "cattivi" seguiranno questa interfaccia:

public interface Monster {
public String getName();
public int fight();
}


ConcreteProduct

Ecco qua l'esempio di uno dei mostri che popolano la nostra applicazione:

public class TinMan implements Monster {
private int strength = 3;

public String getName() {
return this.getClass().getSimpleName();
}

public int fight() {
return strength;
}
}


Test

Questo é un piccolo tester per questa implementazione del pattern:

BadGuy z = new Zardoz();
Monster monster = z.createMonster(BadGuy.MonsterType.SIMPLE);
System.out.println("Monster " + monster.getName() + " created.");
System.out.println("Monster is fighting: " + monster.fight());

BadGuy t = new Topolonek();
monster = t.createMonster(BadGuy.MonsterType.STRONG);
System.out.println("Monster " + monster.getName() + " created.");
System.out.println("Monster is fighting: " + monster.fight());

Notiamo che, dal punto di vista del cliente, quello che ci interessa sapere é solo il "cattivo" con cui abbiamo a che fare e il livello del mostro che vogliamo generare. Il cliente opera sull'interfaccia BadGuy, ma la decisione sul tipo effettivo di mostro creato é delegato alle classi che la implementano.

Il risultato del nostro tester sarà il seguente:

Monster TinMan created.
Monster is fighting: 3
Monster Pippok created.
Monster is fighting: 18

Decorator

Pattern descritto in Design Pattern.

Lo scopo del pattern Decorator é aggiungere responsabilità aggiuntive ad un oggetto dinamicamente; si pone quindi come alternativa alla flessibilità ottenuta aggiungendo funzionalità creando sottoclassi.

A volte si vogliono aggiungere funzionalità solo a certi oggetti partecipanti a un sistema, non ad una intera classe. Si può ottenere questo effetto per mezzo dell'ereditarietà, che però porta ad una scelta statica sugli oggetti con le differenti funzionalità.

Un approccio più flessibile é quello di includere la componente a cui vogliamo aggiungere funzionalità in un altro oggetto che ha per l'appunto questo scopo.

Si usa un decorator quando:
  • si vogliono aggiungere responsabilità ad oggetti individuali in modo dinamico e trasparente;
  • le responsabilità possono essere tolte;
  • non é pratico usare sottoclassi.
Partecipanti
  • Component: tipicamente una classe astratta, definisce l'interfaccia per gli oggetti a cui vogliamo aggiungere responsabilità;
  • ConcreteComponent: classe concreta, ai cui oggetti possono essere aggiunte responsabilità;
  • Decorator: tipicamente una classe astratta, mantiene un riferimento all'oggetto concreto e definisce un'interfaccia conforme a Component;
  • ConcreteDecorator: aggiunge responsabilità ad una componente.
Esempio in Java

Continuiamo a ragionare sulla nostra applicazione fantasy, ora vogliamo considerare i combattimenti tra diversi personaggi. Un guerrierio avrà una certa capacità offensiva di base che verrà incrementata a seconda delle armi che usa nel combattimento.

Ad esempio può succedere che un guerriero cominci a combattere a mani nude, per poi continuare con una spada, e magari aggiungere poi anche un coltello alle sue armi.

Per ottenere questo risultato pensiamo ad una gerarchia di classi strutturata in questo modo:
  • Warrior, é la Component su cui é basata la struttura;
  • NormalWarrior, é la ConcreteComponent;
  • WarriorDecorator, é il Decorator;
  • Sword e Dagger, sono due classi che agiscono da ConcreteDecorator.
Component

La classe astratta Warrior definisce l'interfaccia per i guerrieri, in particolare ci dice che il metodo fight() ritornerà un valore intero indicante la forza d'attacco del guerriero:

public abstract class Warrior {
protected String name;

public String getName() { return name; }

public abstract int fight();
}

ConcreteComponent

La classe che definisce il guerriero concreto NormalWarrior estende Warrior implementando il metodo fight():

public class NormalWarrior extends Warrior {
private int strenght;

public NormalWarrior(String name, int strenght) {
super.name = name;
this.strenght = strenght;
}

public int fight() {
return strenght;
}
}

Decorator

La classe astratta WarriorDecorator estende Warrior introducendo un riferimento a quella che sarà il guerriero concreto gestito dal decorator concreto e elementi utilizzati dai decorator concreti per implementare le funzionalità richieste:

public abstract class WarriorDecorator extends Warrior {
protected Warrior warrior;

protected int shot;
protected String description;
}


ConcreteDecorator

Abbiamo due decorator concreti, una spada e un coltello, che possono essere usati dai guerrieri per incrementare la loro potenza d'attacco. L'implementazione delle due classi varia così poco che ne mostro solo una, la spada:

public class Sword extends WarriorDecorator {
final int DEFAULT_SHOT = 8;
final String DEFAULT_DESCRIPTION = "normal sword";

public Sword(Warrior warrior) {
this.warrior = warrior;
this.shot = DEFAULT_SHOT;
this.description = DEFAULT_DESCRIPTION;
}

@Override
public String getName() {
return warrior.getName() + ", " + description;
}

@Override
public int fight() {
return warrior.fight() + shot;
}
}

Si noti che il comportamento proprio della classe Warrior viene ridefinito in Sword in modo da appoggiarsi sul riferimento al guerriero passato alla classe nel costruttore.

Test

Per verificare il codice che abbiamo scritto usiamo questo piccolo test client:

Warrior tim = new NormalWarrior("Tim", 3);

// start fighting
System.out.println(tim.getName());
System.out.println("First shot: " + tim.fight());

// take a sword
tim = new Sword(tim);
System.out.println(tim.getName());
System.out.println("Second shot: " + tim.fight());

// take a dagger, too
tim = new Dagger(tim);
System.out.println(tim.getName());
System.out.println("Third shot: " + tim.fight());

Creiamo inizialmente un semplice guerriero, che inizia a combattere con le sue sole forze. Poi prende una spada, quindi un coltello. L'output del test é questo:

Tim
First shot: 3
Tim, normal sword
Second shot: 11
Tim, normal sword, normal dagger
Third shot: 15

Il nome e la potenza d'attacco sono modificati con l'aggiunta dei decorator, come volevamo.

Composite

Pattern descritto in Design Pattern.

Lo scopo del pattern Composite é permettere al client di gestire oggetti e gruppi di oggetti uniformemente in una struttura ad albero.

Partecipanti
  • Client: gestisce gli oggetti nella struttura per mezzo dell'interfaccia Component.
  • Component: dichiara l'interfaccia degli oggetti nel gruppo composto; implementa il comportamento di default per tutte le classi nella struttura; dichiara un'interfaccia per accedere e gestire le componenti figlie; se necessario può definire un'interfaccia per risalire la struttura ad albero.
  • Foglia: elemento terminale della struttura ad albero.
  • Composite: definisce il comportamento per componenti che hanno figli; contiene i le componenti figlie; implementa le operazioni per la gestione dei figli dichiarate nella interfaccia Component.
Un punto delicato di questo pattern é il fatto che Component deve nascondere al client il tipo reale dell'oggetto sottostante, sia esso un Composite o una foglia. Ma questo va in conflitto con un criterio di base della progettazione Object-Oriented, dato che siamo forzati a dichiarare in Component metodi che non hanno senso a quel livello. E infatti la classe foglia si trova a dover implementare metodi che non hanno alcuna utilità in quel contesto. Una semplice soluzione é di dare una implementazione di default ai metodi in Component che sollevi un'eccezione.

Esempio in Java

Torniamo al nostro progetto fantasy. Vogliamo poter create compagnie che possano partire per favolose missioni. Dal punto di vista del giocatore, la sua compagnia é a volte vista come un unico complesso che agisce ai suoi comandi, anche se spesso é composta da elementi che hanno caratteristiche ben differenziate.

In questo caso potrebbe essere opportuno implementare la compagnia usando questo pattern.

Implementiamo un caso molto semplice, in cui gli elementi della nostra compagnia son tutti umani, e vogliamo distinguerli in due soli sottogruppi, uno composto da persone normali e uno da guerrieri. Questi ultimi sono allenati al combattimento ma, per quel che ci interessa ora, sono molto più lenti nei movimenti, in quanto appesantiti dalle loro armi.

Le classi coinvolte nel nostro esempio saranno dunque queste:
  • Man: é una foglia del pattern, definisce il comportamento degli umani
  • Warrior: anch'essa foglia del pattern, specializza Man
  • Group: é il Composite del pattern, gestisce i gruppi all'interno della compagnia
  • GroupComponent: é il Component del pattern
Vediamo il codice, iniziando da GroupComponent:

public abstract class GroupComponent {
protected String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public void add(GroupComponent component) {
throw new UnsupportedOperationException();
}

public void remove(GroupComponent component) {
throw new UnsupportedOperationException();
}

public void list() {
throw new UnsupportedOperationException();
}

public int getVelocity() {
throw new UnsupportedOperationException();
}
}

La classe é astratta, dunque non possiamo istanziare oggetti di questo tipo. Ogni elemento nella compagnia ha un nome, e quindi mettiamo qui il membro name e i suoi getter e setter. C'é poi ci sono i metodi con la loro implementazione di default, che si riduce a lanciare un'eccezione.

Vediamo ora l'implementazione della foglia:

public class Man extends GroupComponent {
private final int DEFAULT_VELOCITY = 8;
int velocity;

public Man(String name, int bonus) {
super.name = name;
this.velocity = DEFAULT_VELOCITY + bonus;
}

@Override
public int getVelocity() {
return velocity;
}

public void setVelocity(int velocity) {
this.velocity = velocity;
}

@Override
public void list() {
System.out.println("Man name: " + name + ", velocity " + velocity);
}
}

I membri "reali" della compagnia hanno una certa velocità, che é tipica della classe, ma che può essere modificata dal costruttore. Due metodi della GroupComponent vengono ridefiniti qui, chiamare altri metodi di quella classe in questo contesto genererà un eccezione.

Per i guerrieri useremo questa specializzazione:

public class Warrior extends Man {
public Warrior(String name, int bonus) {
super(name, bonus);
super.velocity /= 2;
}
}

Cambia solo il costruttore, che causa una penalità alla velocità del personaggio.

La classe Group ci permette di rappresentare i diversi gruppi omogenei all'interno della nostra compagnia. Ridefinirà quindi i metodi di GroupComponent per permettere di gestire i suoi nodi:

import java.util.ArrayList;
import java.util.Iterator;

public class Group extends GroupComponent {
ArrayList components = new ArrayList();

public Group(String name) {
super.name = name;
}

@Override
public void add(GroupComponent component) {
components.add(component);
}

@Override
public void remove(GroupComponent component) {
components.remove(component);
}

@Override
public void list() {
System.out.println("\nGroup Name: " + name);

Iterator it = components.iterator();
while(it.hasNext()) {
it.next().list();
}
}

@Override
public int getVelocity() {
int velocity = Integer.MAX_VALUE;
Iterator it = components.iterator();
while(it.hasNext()) {
int candidate = it.next().getVelocity();
if(velocity > candidate)
velocity = candidate;
}
return velocity;
}
}

Notiamo che il metodo list() itera su tutti i nodi posseduti da questo gruppo, mostrando quindi i dati di base per tutti gli elementi.
La getVelocity() ritorna la velocità dell'elemento più lento del gruppo, dato che sarà lui a determinare la velocità di tutto il gruppo. Nota che se non ci fosse alcun elemento all'interno del gruppo, la velocità del gruppo sarebbe la massima consentita, caratteristica bizzarra che potrebbe anche essere utile in un contesto fantasy.

Testiamo questo schema con questo piccolo client di prova:

Group thieves = new Group("Thieves");
Man tom = new Man("Tom", 3);
Man bill = new Man("Bill", 5);
Man will = new Man("Will", 4);
thieves.add(tom);
thieves.add(bill);
thieves.add(will);

Group warriors = new Group("Warriors");
Warrior mark = new Warrior("Mark", 4);
Warrior bud = new Warrior("Bud", 2);
Warrior jepp = new Warrior("Jepp", 6);
warriors.add(mark);
warriors.add(bud);
warriors.add(jepp);

Group group = new Group("Looking for something");
group.add(thieves);
group.add(warriors);

group.list();
System.out.println("\nThe company velocity: " + group.getVelocity());

Gran parte del codice rappresenta il setup della struttura, le ultime due righe sono il testing vero e proprio. Viene fatto un printout di tutti gli elementi inseriti nel nostro albero e poi viene stampata la velocità della compagnia.

Adapter - Wrapper

Pattern descritto in Design Pattern.

Lo scopo dell'Adapter é quello di permettere l'uso di una classe da parte di un'altra classe che non era stata pensata per interagire con quella, e che quindi si aspetta una diversa interfaccia.

Un esempio preso dalla vita reale é quello del nostro rasoio elettrico che possiamo usare in un albergo londinese solo a patto di adattare la presa della corrente inglese alla spina italiana.

Partecipanti
  • Client: utente delle funzionalità
  • Target: interfaccia che fornisce l'interfaccia al Client
  • Adaptee: l'interfaccia precedentemente esistente, non adatta al Client
  • Adapter: adatta l'interfaccia Adaptee a Target.
Esempio in Java

Immaginiamo di lavorare ad un applicazione che simuli un mondo fantasy. Ci sono uomini che mangiano e camminano e giganti che divorano e si muovono tre volte tanto quanto un uomo normale.

Fin qui tutto facile. Il fatto é che esistono anche strani esseri che a volte si comportano come uomini a volte come giganti. Costruiamo quindi un Adaptor che ci permetta di gestire questa situazione.

Un uomo rispetterà questa interfaccia:

public interface Man {
public void eat();
public void walk();
}

Un gigante seguirà invece questa:

public interface Giant {
public void devour();
public void bigWalk();
}

Questa classe definisce il comportamento di un uomo normale:

public class NormalMan implements Man {
public void eat() {
System.out.println("Eating something");
}

public void walk() {
System.out.println("Short walking");
}
}

Questa invece definisce un normale gigante:

public class NormalGiant implements Giant {
public void devour() {
System.out.println("Eating a lot");
}

public void bigWalk() {
System.out.println("Long walking");
}
}

E qui arriviamo al nostro Adaptor, uno strano essere uomo-gigante che estende l'uomo normale ma implementa l'interfaccia del gigante e definisce i metodi caratteristici del gigante in base a quelli dell'uomo:

public class ManGiant extends NormalMan implements Giant {
public void devour() {
System.out.println("A man eating like a giant:");
super.eat();
super.eat();
super.eat();
}

public void bigWalk() {
System.out.println("A man walking like a giant:");
super.walk();
super.walk();
super.walk();
}
}

A seguire un piccolo client per testare le funzionalità:

public class Main {
public static void main(String args[]) {
Man man = new NormalMan();
man.eat();
man.walk();

Giant giant = new NormalGiant();
giant.devour();
giant.bigWalk();

ManGiant mg = new ManGiant();

// now man-giant behaves like a giant
giant = mg;
giant.devour();
giant.bigWalk();

// now man-giant behaves like a man
man = mg;
man.eat();
man.walk();
}
}

Notiamo che il nostro uomo-gigante può comportarsi in entrambi i modi. Possiamo chiarire il suo comportamento in un dato momento usando l'interfaccia Man o Giant, invece di accedere direttamente all'oggetto.

Abstract Factory - Kit

Pattern descritto in Design Pattern.

L'idea dell'Abstract Factory é che spesso si devono costruire famiglie diverse di oggetti che dipendono tutti da una scelta iniziale.

Ad esempio, se vogliamo che la nostra applicazione supporti diversi look-and-feel, potremmo volere che i vari oggetti della GUI vengano tutti creati da una factory specifica e non vogliamo che il nostro client si debba stare a preoccupare tanto di tutto ciò.

Così, per creare una finestra, si chiamerà il metodo createWindow() della nostra Abstract Factory. Noi avremo in precendenza istanziato la factory concreta che fa al caso nostro, e quindi la chiamata si risolverà implementando la finestra per la corretta modalità.

Partecipanti
  • AbstractFactory: dichiara l'interfaccia per la creazione degli oggetti.
  • ConcreteFactory: esiste una classe di questo tipo per ogni famiglia di oggetti che vogliamo creare.
  • AbstractProduct: ogni tipo di oggetti che vogliamo creare dovrà rispettare questa interfaccia.
  • ConcreteProduct: la classe che definisce le specifiche dell'oggetto da creare.
  • Client: per creare gli oggetti usa solo le interfacce AbstractFactory e Abstract Product.
Implementazione

Dato che tipicamente esiste sono una ConcreteFactory per esecuzione, risulta normalmente naturale implementarla usando il pattern Singleton.

La creazione dei prodotti avviene nella ConcreteFactory, tipicamente usando il pattern Factory Method.

Se ci sono molte famiglie si può fare ricorso al pattern Prototype per ridurre la complessità del sistema.

Per ridurre l'impatto del cambiamento delle factories, é possibile usare un metodo generico "make" a cui viene passato un parametro che specifica il tipo dell'oggetto che si vuole effettivamente creare. In questo caso si paga la maggior flessibilità con una minore sicurezza. Chiaramente é più semplice implementare questa soluzione in un linguaggio dinamicamente tipizzato, più il linguaggio tende alla staticità dei tipi di dati, più la cosa diventa difficile. In ogni caso spesso può essere necessario fare un downcast, che é inerentemente una operazione poco sicura.

Esempio in Java

Nel nostro caso abbiamo tre possibili implementazioni per un sistema di oggetti grafici, che consiste al momento di due soli elementi: Button e Window.
La nostra Abstract Factory sarà perciò qualcosa del genere:

public abstract class A1Factory {
public enum Type { ONE, TWO, THREE };
public static A1Factory getFactory(Type type) {
switch(type) {
case ONE:
return new A1FactoryOne();
case TWO:
return new A1FactoryTwo();
case THREE:
default:
return new A1FactoryThree();
}
}

public abstract Button createButton();
public abstract Window createWindow();
}

L'effettiva costruzione di un oggetto é delegata alla Factory concreta, vediamone qui una possibile implementazione:

public class A1FactoryOne extends A1Factory {

@Override
public Button createButton() {
return new ButtonOne();
}

@Override
public Window createWindow() {
return new WindowOne();
}
}

Ogni oggetto da creare avrà una sua gerarchia, che comprende una interfaccia:

public interface Button {
public void click();
}

E una implementazione specifica per il tipo di sistema utilizzato:

public class ButtonOne implements Button {

public void click() {
System.out.println("Click One");
}
}

Per testare il sistema possiamo scrivere un client come questo:

public class Main {
public static void main(String[] args) {
for(A1Factory.Type t: A1Factory.Type.values()) {
A1Factory f = A1Factory.getFactory(t);
System.out.println("Factory " + t);
f.createButton().click();
f.createWindow().drag();
}
}
}

Un secondo esempio in Java

Forziamo adesso un po' il pattern per usare la reflection di Java.

Nel nostro caso le Factory concrete ci servono solo per tenere l'informazione di quale tipo di gerarchia di classi dobbiamo instanziare. In realtà questa informazione la possiamo tenere nella Factory madre, che possiamo quindi far diventar concreta, rinunciando a tutte le Factory figlie.
Usiamo inoltre il metodo generico make(), indicato dalla GoF come possibile alternativa per ridurre l'impatto al cambiamento, utilizzando come parametro selettore l'interfaccia alla classe che vogliamo creare.
Come terza variazione, introduciamo una interfaccia, Widget, che sta alla base della gerarchia di oggetti che possono essere creati dalla nostra Factory. In questo modo possiamo rendere meno insicuro l'uso del metodo generico make(), in quanto possiamo restringere il suo campo d'uso a Widget e classi derivate. Ecco quindi la nostra nuova factory:

public class A2Factory {
public enum Type { One, Two, Three };
private Type type;

public A2Factory (Type type) {
this.type = type;
}

public Widget make(Class clazz) throws ClassNotFoundException {
try {
String name = clazz.getCanonicalName() + type;
System.out.println(name);
Widget w = (Widget) (Class.forName(name)).newInstance();
return w;
}
catch(Exception ex) {
throw new ClassNotFoundException(ex.getMessage());
}
}
}

Certamente un punto debole di questa implementazione é che viene usata una naming convention sulle classi generate. Gli oggetti della famiglia "One" devono avere tutti quella desinenza quindi, ad esempio, il suo bottone si chiamerà obbligatoriamente "ButtonOne".
Ma penso che sia una limitazione che generalmente si possa accettare.

L'interfaccia Widget potrebbe essere definita in questo modo:

public interface Widget {
public void paint();
}

E quindi l'interfaccia alla base della gerarchia di oggetti diventa qualcosa di simile a questa:

public interface Window extends Widget {
public void drag();
}

Mentre la classe che definisce un widget sarà circa così:

public class WindowOne implements Window {

public void paint() {
System.out.println("Window One");
}

public void drag() {
System.out.println("Drag One");
}
}

A seguire, un piccolo client per provare il sistema:

public class Main {
public static void main(String[] args) {
for(A2Factory.Type t: A2Factory.Type.values()) {
A2Factory f = new A2Factory(t);

System.out.println("Factory " + t);

Widget button = null;
Widget window = null;
try {
button = f.make(Button.class);
window = f.make(Window.class);
}
catch (ClassNotFoundException ex) {
System.out.println("ClassNotFoundException: " + ex.getMessage());
return;
}

// just basic widget behaviour required
window.paint();
button.paint();

// specialized behaviour required
if(window instanceof Window)
((Window)window).drag();
if(button instanceof Button)
((Button)button).click();

// trying making something wrong
try {
Object o = f.make(Object.class);
}
catch(ClassNotFoundException ex) {
System.out.println("As expected, a ClassNotFoundException: " + ex.getMessage());
}
}
}
}

Come si vede, il codice risultante é più complesso di quello del primo caso, dato che dobbiamo tener conto dei possibili utilizzi scorretti del sistema, e quindi cautelarci con una opportuna gestione delle eccezioni.

In cambio abbiamo una miglior adattabilità del sistema al cambiamento.

Design Patterns

Approfittando del tempo libero a disposizione, mi rimetto a leggere un classico: "Design Patterns: Elements of Reusable Object-Oriented Software" di Erich Gamma
Richard Helm, Ralph Johnson e John M. Vlissides. Ormai noti nel campo come la banda dei quattro (GoF: Gang of Four).

Il libro non é certo fresco di stampa (pubblicato nell'ottobre 1994, ci avviciniamo ai quindici anni!) ma resta comunque una lettura fondamentale.