Wzorzec odwiedzający należy rozważyć w momencie, gdy mamy do czynienia z wyraźnym podziałem wykonywanych operacji w zależności od obiektu, operacje mają zawiłą logikę. Efektem zastosowania wzorca jest oddzielenie implementacji operacji (przechowywane w instancji clasy Visitor) od struktur danych na których operujemy.
Przykłady, w których warto Rozpatrzeć wzorzec Visitor to wszelkiego typu parsery:
Oddzielne operacje dla: tagów, atrybutów, namespace’ów, textu, elementów, prefixów
Tutaj również mnogość występujących znaków - *, +, /, -, mod, nawiasy z kolejnością wykonwyania działań, funkcje specjalne itd…
Wzorzec Visitor opiera się na zasadzie, że dodawanie nowych klas (operacji) jest stosunkowo łatwe i nie powoduje potrzeby rekompilowania kodu w miejscach w których nic nie zmienialiśmy. Dodawanie natomiast nowych metod wirtualnych rodzi wiele potencjalnych problemów:
W przykładzie zaprezentowany jest problem wykonywania operacji na różnych eventach w grze RPG np Level Up, czy Znalezienie przedmiotu. Chcemy aktualizowac statystyki postaci w zaleznosci od tych wydarzen.
Dodanie kolejnej operacji wiąże się z dodaniem metody wirtualnej w wszyskimi związanymi z nią “obowiązkami”
class Event {
public:
virtual void UpdateStats(GameCharacterStats& stats) = 0;
// Inne mozliwe
// virtual void UpdateExternalAppearance ..
// virtual void UpdateAttrbutes ...
// virtual void UpdateSkills ...
virtual ~Event() = default;
};
class LevelUp : public Event {
public:
void AddLevel();
void UpdateStats(GameCharacterStats& stats) override; // Event
};
class LootFound : public Event {
public:
void UpdateStats(GameCharacterStats& stats) override; // Event
};
Bardzo szybko może okazać się, że nasz interfejs jest nieadekwatny, wiele Eventów czegoś nie implementuje. Pojawia się coraz więcej przypadków szczególnych - za chwilę skończymy z drabinką if else i dynamic_cast…
Przechowuje on logikę wykonywanych operacji w zależności od eventu:
class EventVisitor {
public:
virtual void VisitLevelUp(LevelUp& lvlUp) = 0;
virtual void VisitLootFound(LootFound& lootFound) = 0;
}
class Event {
public:
virtual void Accept(EventVisitor& visitor) = 0;
};
class LevelUp : public Event {
public:
void AddLevel();
void Accept(EventVisitor& visitor) {
visitor.VisitLevelUp(*this)
};
Teraz dodanie nowej operacji nie wymaga ingerencji w klasy komponentowe (Event)! Wystarczy stworzyć nowy Visitor implementujący oryginalny interfejs :)
Dlatego należy rozróżnić, co jest faktycznie komponentem a co operacją i czy będzie nam się to opłacać. (Np dodanie kolejnego eventu typu “CharacterDeath” wiąże się z nakładem pracy.
Visitor “wie” o komponentach, ale nie może korzystać z metod prywatnych więc może to wymagać sporych zmian w logice.