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
---

Nessun commento:

Posta un commento