Odwiedzający (Visitor)
Zadaniem jest "odwiedzenie" każdego elementu w danej strukturze obiektów
i wykonanie na nim konkretnych działań.
Różne implementacje wizytatorów, mogą wykonywać różne zadania,
rozszerzając funkcjonalność struktury elementów, bez ich wewnętrznej modyfikacji.
Przykład
Program będzie dotyczył tworzenia kształtów takich jak kwadrat, prostoskąt,
elispa i kształt złożony mogący składać się z dwóch kształtów.
Zadaniem wizytatora będzie odwiedzenie każdego kształtu, a następnie
wyświetlenie o nim danych, np. w przypadku elipsy jej dwóch promieniów.
Wszystkie kształty będą dziedziczyły po klasie Shape (kształt).
W poniższym przykładzie przedstawie tylko najważniejsze fragmenty kodu.
1. Na początek tworzymy klase Shape. Najważaniejsza w tej klasie dla naszych potrzeb
jest metoda accept(). Przyjmuje ona wizytatora. Narazie jest czysto wirtualna.
Implementować ją będziemy dopiero w klasach dziedziczących po Shape.
class Shape { // ... public: virtual void accept(Visitor &p_visitor) = 0; // ... };
2. Pierwsza klasa dziedzicząca po Shape. Mamy tutaj dwa pola określające położenie
prostokąta oraz metode accept(), którą teraz trzeba będzie zaimplementować.
class Rectangle : public Shape { // ... public: std::pairxy; std::pair xy2; virtual void accept(Visitor &p_visitor); };
3. Klasa Rectangle.cpp. Najważniejsze fragmenty.
Metoda accept() jak już wcześniej zostało wspomniane przyjmuje wizytatora. Wizytator
jak się już nie długo przekonamy będzie implementował metodę visit(), do której przyjmować
będzie konkretny rodzaj kształtu - shape-a. Dlatego więc do wizyt przekazujemy poniżej
*this bo chcemy przekazać obiekt (samego siebie) rectangl-a do metody visit wizytatora,
żeby ten mógł np. wyświetlić dane prostokąta.
void Rectangle::accept(Visitor &p_visitor) { p_visitor.visit(*this); }
4. Analogicznie sprawa wygląda dla klasy Ellipse z tą różnicą, że elipsa posiada dwa promienie
oraz swoje położenie.
class Ellipse : public Shape { // ... public: std::pairxy; float rx, ry; virtual void accept(Visitor &p_visitor); };
5. Na podobnej zasadzie, jak w prostokącie zaimplementowana została metoda accept() w elipsie.
Prosze spojrzeć.
void Ellipse::accept(Visitor &p_visitor) { p_visitor.visit(*this); }
6. Klasa complexShape jest nieco bardziej złożona, ponieważ składa się conajmniej z dwóch
kształtów. Nawet niekoniecznie podstawowych, ponieważ jeden z podkształtów albo nawet dwa
mogą być również kształtami złożonymi. Z tego więc powodu klasa ComplexShape przechowuje
dwa wskaźniki na Shape-y oraz standardowo metode accept().
class ComplexShape : public Shape { // ... public: Shape* shape1; Shape* shape2; virtual void accept(Visitor &p_visitor); };
7. Implementacja metody accept jest nieco inna od np. implementacji w klasie Rectangle.
W klasie ComplexShape przechowujemy dwa kształty, które mogą być jeszcze złożone więc
należy każdy z tych kształtów odwiedzić.
Trzeba najpierw wywołać metody accept() na wewnętrznych kształtach, a następnie wywołąć
metodę visit() na wizytatorze przekazanym przez argument funkcji.
Będzie tutaj działała rekurencja.
void ComplexShape::accept(Visitor &p_visitor) { shape1->accept(p_visitor); shape2->accept(p_visitor); p_visitor.visit(*this); }
8. Teraz czas na klase wizytatora. Najważniejsze jest to, że przechowuje on trzy metody visit.
Każda z nich różni się od reszty tym, iż przyjmuje różne kształty jako argumenty,
a właściewie to wszystkie, które zaimplementowaliśmy wyżej. To dzięki temu zabiegowi
jeżeli w powyższych metodach accept(), do których przekażemy wizytatora, a następnie wywołamy
visit(*this). Właśnie dzięki temu, że zdefiniowaliśmy trzy metody visit(), przyjmujące różne
kształy nasza wizytator będzie wiedział, który kształt został przyjęty i odpowiednio do tego się
zachowa. Narazie tylko interfejs wizytatora. Konkretna specjalizacja wizytatora będzie poniżej.
class Visitor { public: Visitor(); virtual ~Visitor(); virtual void visit(const Rectangle &p_rectangle) = 0; virtual void visit(const Ellipse &p_ellipse) = 0; virtual void visit(const ComplexShape &p_complexShape) = 0; };
9. Konkretna spoecjalizacja wizytatora. Będzie to wizytator drukujący.
class PrinterVisitor : public Visitor { public: PrinterVisitor(); virtual ~PrinterVisitor(); virtual void visit(const Rectangle &p_rectangle); virtual void visit(const Ellipse &p_ellipse); virtual void visit(const ComplexShape &p_complexShape); };
10. Implementacja metod. Każda na swój sposób, ponieważ każdy z kształtów przechowuje
inne dane.
void PrinterVisitor::visit(const Rectangle &p_rectangle) { std::cout << " Rectangle : " << std::endl; std::cout << "p_rectangle.xy.first = " << p_rectangle.xy.first << std::endl; std::cout << "p_rectangle.xy.second = " << p_rectangle.xy.second << std::endl; } void PrinterVisitor::visit(const Ellipse &p_ellipse) { std::cout << " Ellipse : " << std::endl; std::cout << "p_ellipse.xy.first = " << p_ellipse.xy.first << std::endl; std::cout << "p_ellipse.xy.second = " << p_ellipse.xy.second << std::endl; } void PrinterVisitor::visit(const ComplexShape &p_complexShape) { std::cout << " ComplexShape : " << std::endl; std::cout << "p_complexShape.shape1->rotatedByAlpha = " << p_complexShape.shape1->rotatedByAlpha << std::endl; std::cout << "p_complexShape.shape2->rotatedByAlpha = " << p_complexShape.shape2->rotatedByAlpha << std::endl; }
11. Na koniec main programu - użycie wizytatora.
PrinterVisitor printerV; rectangle.accept(printerV); elipse1.accept(printerV);
Diagram klas:
Brak komentarzy:
Prześlij komentarz