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

Nessun commento:

Posta un commento