kuchichan / CS-Visitor

Presentation of Visitor Pattern for Coders School purposes.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Wzorzec Odwiedzający (Visitor)

Wstęp

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:

Parser xml

Oddzielne operacje dla: tagów, atrybutów, namespace’ów, textu, elementów, prefixów

Calculator

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:

Rekompilacje kodu w wielu miejscach

Zbyt cięzkie interfejsy (Problem z Liskov)

Problem z Open/Closed modyfikowanie istniejącego kodu

Przykład

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.

Wersja naiwna:

Podstawowym problemem jest “rozrzucenie” operacji Aktualizacji Statystyk na różne klasy Eventów.

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…

Refactor

Pierwszą rzeczą jaką musimy zrobić, jest sworzenie klasy visitora

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

W konkretnych Eventach (zwanych ogolnie komponentami) zmieniamy interfejs:

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

Wady:

Dorzucenie eventu (komponentu) powoduje zmiany we wszystkich wizytorach

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.

Podczas refatoru, jeśli klasa komponentowa posiada wiele metod / pól prywatych - dodatkowa praca.

Visitor “wie” o komponentach, ale nie może korzystać z metod prywatnych więc może to wymagać sporych zmian w logice.

About

Presentation of Visitor Pattern for Coders School purposes.


Languages

Language:C++ 91.6%Language:CMake 8.4%