Wzorzec „Strategia”

Jednym z podstawowych wzorców projektowych jest wzorzec o nazwie „Strategia”. Zgodnie z definicją określa on rodzinę algorytmów, które umieszczane są w odrębnych klasach i dzięki temu są one w pełni wymienne.

Przeanalizujmy tą definicję na pewnym przykładzie. Chcemy utworzyć namiastkę systemu do gry RPG. Na początek postanawiamy utworzyć kilka profesji postaci: Wojownik, Mag, Złodziej, Kapłan, Berserk, Paladyn, Łowca, Druid. Postacie mogą używać różnorodnych broni do walki, jak na przykład: miecz, kij, topór, sztylety, łuk. Dodatkowo, część z nich może używać czarów, np.: Kula Ognia, Leczenie, Magiczne Pociski, Morale oraz posiadać specjalne umiejętności, np.: Skradanie, Szał Bitewny, Przemiana w zwierzę. Stosując wzorzec „Strategia” możemy w pewnym stopniu uodpornić nasz kod na zmiany, które zapewne nadejdą, gdy postanowimy dodać nową profesję lub zachowanie. Możemy zrobić następującą rzecz: utworzymy klasy definiujące sposoby walki bronią, które będą implementowały wspólny interfejs. Podobnie zrobimy w przypadku czarów oraz specjalnych umiejętności. Natomiast profesje będą dziedziczyły zachowania po nadrzędnej klasie – Postać, do której zostaną podpięte wyżej wspomniane interfejsy. Dzięki tym zabiegom uzyskamy możliwość przypisywania wybranych umiejętności do konkretnych postaci. Dodatkowo, zależności te będziemy mogli przydzielać dynamicznie, bez potrzeby modyfikacji istniejącego kodu.

Schemat projektu:


Na początek utwórzmy klasę główną – Postać:

public abstract class Character {

    FightingWithWeapon fightingWithWeapon;
    SpellCasting spellCasting;
    SpecialAbilities specialAbilities;

    public abstract void printCharacterName();

    public void makeWeaponAttack() {
        fightingWithWeapon.attack();
    }

    public void chooseAndCastSpell() {
        spellCasting.castSpell();
    }

    public void useSpecialAbility() {
        specialAbilities.useAbility();
    }

    public void setFightingWithWeapon(FightingWithWeapon chosenWeapon) {
        fightingWithWeapon = chosenWeapon;
    }

    public void setSpellCasting(SpellCasting chosenSpell) {
        spellCasting = chosenSpell;
    }

    public void setSpecialAbilities(SpecialAbilities chosenAbility) {
        specialAbilities = chosenAbility;
    }
}

Jak widzimy klasa główna zawiera odwołania do trzech interfejsów grupujących sposoby walki bronią, rzucania czarów oraz specjalne umiejętności. Trzy ostatnie (podświetlone) metody w tej klasie będą służyły do dynamicznego przydzielania zachowań dla postaci. Przykładowy kod dla klasy dziedziczącej z klasy Postać wygląda następująco:

public class Mage extends Character {

    public Mage() {
        spellCasting = new CastingFireBall();
        fightingWithWeapon = new StickFighting();
    }

    public void printCharacterName() {
        System.out.println("Profesja: Mag.");
    }
}

Kody dla wspomnianych wcześniej interfejsów oraz przykładowych klas implementujących te interfejsy prezentują się następująco:

1. Interfejs oraz klasa określająca sposób walki bronią.

public interface FightingWithWeapon {

    void attack();
}
public class SwordFighting implements FightingWithWeapon {

    public void attack() {
        System.out.println("Walka mieczem.");
    }
}

2. Interfejs oraz klasa określająca rzucanie czarów.

public interface SpellCasting {

    void castSpell();
}
public class CastingMagicBullets implements SpellCasting {

    public void castSpell() {
        System.out.println("Rzucono czar: Magiczne Pociski.");
    }
}

3. Interfejs oraz klasa określająca specjalne umiejętności postaci.

public interface SpecialAbilities {

    void useAbility();
}
public class WarFuryAbility implements SpecialAbilities {

    public void useAbility() {
        System.out.println("Szał bitewny!");
    }
}

Po utworzeniu odpowiednich klas możemy napisać kod sprawdzający działanie naszego programu. Kod testowy zawiera odwołania do pozostałych profesji, broni, czarów oraz umiejętności. Na stronie zostały umieszczone jedynie przykładowe klasy podrzędne, lecz ogólna zasada postępowania podczas pisania kodu jest taka sama jak w powyższych przykładach.

public class StrategyPatternTest {

    public static void main(String[] args) {
        Character berserk = new Berserk();
        berserk.printCharacterName();
        berserk.useSpecialAbility();
        berserk.makeWeaponAttack();

        Character mage = new Mage();
        System.out.println("");
        mage.printCharacterName();
        mage.chooseAndCastSpell();
        mage.setSpellCasting(new CastingMagicBullets());
        mage.chooseAndCastSpell();
        mage.makeWeaponAttack();

        Character thief = new Thief();
        System.out.println("");
        thief.printCharacterName();
        thief.useSpecialAbility();
        thief.makeWeaponAttack();

        Character warrior = new Warrior();
        System.out.println("");
        warrior.printCharacterName();
        warrior.makeWeaponAttack();
        warrior.setFightingWithWeapon(new AxeFighting());
        warrior.makeWeaponAttack();

        Character paladin = new Paladin();
        System.out.println("");
        paladin.printCharacterName();
        paladin.chooseAndCastSpell();
        paladin.makeWeaponAttack();
        paladin.setSpellCasting(new CastingHealing());
        paladin.chooseAndCastSpell();

        Character druid = new Druid();
        System.out.println("");
        druid.printCharacterName();
        druid.makeWeaponAttack();
        druid.chooseAndCastSpell();
        druid.useSpecialAbility();

        Character hunter = new Hunter();
        System.out.println("");
        hunter.printCharacterName();
        hunter.makeWeaponAttack();
        hunter.useSpecialAbility();
    }
}

Otrzymany tekst z konsoli:

Profesja: Berserk.
Szał bitewny!
Walka toporem.

Profesja: Mag.
Rzucono czar: Kula Ognia.
Rzucono czar: Magiczne Pociski.
Walka kijem.

Profesja: Złodziej.
Skrada się.
Walka sztyletami.

Profesja: Wojownik.
Walka mieczem.
Walka toporem.

Profesja: Paladyn.
Rzucono czar: Morale.
Walka mieczem.
Rzucono czar: Leczenie.

Profesja: Druid.
Walka kijem.
Rzucono czar: Leczenie.
Przemiana w zwierzę.

Profesja: Łowca.
Strzelanie z łuku.
Przemiana w zwierzę.

 

Źródła literaturowe:
[1] Eric Freeman, Elisabeth Freeman, Kathy Sierra, Bert Bates – „Wzorce projektowe. Rusz głową!”.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *