niedziela, 2 marca 2014

Odwiedzający (obiektowy)

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::pair xy;
 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::pair xy;
 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