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.

Nessun commento:

Posta un commento