Wzorzec „Obserwator”

Wzorzec ten definiuje relację jeden – do wielu pomiędzy obiektami. Jeżeli obiekt obserwowany zmieni swój stan, wszystkie obiekty, które są zależne od tego obiektu dostaną powiadomienie i zostaną automatycznie zaktualizowane.

Podobnie jak w przypadku omawiania wzorca „Strategia”, przykład będzie odnosił się do systemu gier fabularnych RPG. W klasycznych grach tego typu, gdzie teren przedstawiony był w widoku izometrycznym mieliśmy możliwość sterowania postaciami naszej drużyny – jedną lub kilkoma naraz. Działanie wzorca „Obserwator” możemy prześledzić właśnie na takim przykładzie. Jesteśmy graczem i chcemy wydawać komendy naszym postaciom, aby szły w określonym kierunku. Możemy dodawać oraz usuwać postacie, którymi aktualnie chcemy sterować. W momencie wydania komendy, obiekty nasłuchujące powinny wykonać żądane polecenie.

Plan działania jest następujący:
1. Utworzymy interfejs Subject posiadający metody związane z rejestrowaniem, usuwaniem oraz powiadamianiem obiektów. Będzie on implementowany przez klasę Player.
2. Utworzymy również interfejs ObservedCharacter zawierający metodą aktualizującą stan obiektów implementujących ten interfejs. Utworzymy trzy klasy reprezentujące postacie z gry, tj. Wojownika, Maga i Złodzieja.
3. Dodatkowo, utworzymy interfejs Printing, służący do wyświetlania danych w konsoli oraz typ wyliczeniowy TeamDirection określający kierunek, w którym ma udać się drużyna.
4. Na koniec zostanie napisanie kodu sprawdzającego działanie programu.

Interfejs Subject z trzema metodami dotyczącymi rejestrowania, usuwania i powiadamiania obserwatorów.

public interface Subject {

    void registerObserver(ObservedCharacter o);
    void deleteObserver(ObservedCharacter o);
    void notifyObservers();
}

Klasa Player implementująca interfejs Subject. Zawiera ona definicje wyżej wspomnianych metod oraz metodę ustawiającą wybrany kierunek ruchu postaci.

import java.util.ArrayList;

public class Player implements Subject {
    private ArrayList<ObservedCharacter> observers;
    private TeamDirection teamDirection;

    public Player() {
        observers = new ArrayList<>();
    }

    public void registerObserver(ObservedCharacter o) {
        observers.add(o);
    }

    public void deleteObserver(ObservedCharacter o) {
        int i = observers.indexOf(o);
        if (i &amp;gt;= 0) observers.remove(i);
    }

    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++) {
            ObservedCharacter observer = observers.get(i);
            observer.update(teamDirection);
        }
    }

    public void directionHasChanged() {
        notifyObservers();
    }

    public void setTeamDirection(TeamDirection direction) {
        this.teamDirection = direction;
        directionHasChanged();
    }
}

Interfejs zawierający metodę aktualizującą stan obiektów.

public interface ObservedCharacter {

    void update(TeamDirection direction);
}

Interfejs zawierający metodę wyświetlającą tekst w konsoli.

public interface Printing {

    void printDestination();
}

Klasa postaci implementująca dwa powyższe interfejsy. Zawiera definicje metod służących do uaktualniania stanu obiektu oraz do wyświetlania tekstu. Podczas tworzenia instancji tej klasy, obiekt dodawany jest do listy obserwatorów. Poniżej pokazany jest kod dla klasy Warrior. Pozostałe klasy: Mage i Thief różnią się jedynie nazwą klasy i konstruktora.

public class Warrior implements ObservedCharacter, Printing {
    private TeamDirection direction;
    private String name;

    public Warrior(Subject Player, String name) {
        Player.registerObserver(this);
        this.name = name;
    }

    public void update(TeamDirection direction) {
        this.direction = direction;
        printDestination();
    }

    public void printDestination() {
        System.out.println(name + " idzie " + TeamDirection.getDirection(direction));
    }
}

Typ wyliczeniowy określający kierunki ruchu postaci.

public enum TeamDirection {
    LEFT, RIGHT, FORWARD, BACKWARD;

    public static String getDirection(TeamDirection direction) {
        switch(direction) {
            case LEFT: return "w lewo";
            case RIGHT: return "w prawo";
            case FORWARD: return "do przodu";
            case BACKWARD: return "do tyłu";
            default: return null;
        }
    }
}

Kod testowy, w którym zawarte jest dodawanie, usuwanie oraz aktualizacja stanu obiektów.

public class ObserverPatternTest {

    public static void main(String[] args) {
        Player player = new Player();

        Warrior warrior_1 = new Warrior(player, "Wojownik 1");
        Warrior warrior_2 = new Warrior(player, "Wojownik 2");
        Mage mage = new Mage(player, "Mag");
        Thief thief = new Thief(player, "Złodziej");

        player.setTeamDirection(TeamDirection.LEFT);
        System.out.println("");

        player.deleteObserver(warrior_1);
        player.deleteObserver(mage);
        player.deleteObserver(thief);
        player.setTeamDirection(TeamDirection.RIGHT);
        System.out.println("");

        player.registerObserver(mage);
        player.deleteObserver(warrior_2);
        player.setTeamDirection(TeamDirection.FORWARD);
        System.out.println("");

        player.registerObserver(thief);
        player.setTeamDirection(TeamDirection.BACKWARD);
    }
}

Otrzymany tekst z konsoli:

Wojownik 1 idzie w lewo
Wojownik 2 idzie w lewo
Mag idzie w lewo
Złodziej idzie w lewo

Wojownik 2 idzie w prawo

Mag idzie do przodu

Mag idzie do tyłu
Złodziej idzie do tyłu

 

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