Wzorzec projektowy dla zachowania polimorficznego, umożliwiając jednocześnie rozdzielanie bibliotek

10

Powiedzmy mam hierarchię Itemklas: Rectangle, Circle, Triangle. Chcę móc je narysować, więc moją pierwszą możliwością jest dodanie wirtualnej Draw()metody do każdej z nich:

class Item {
public:
   virtual ~Item();
   virtual void Draw() =0; 
};

Chcę jednak podzielić funkcję rysowania na osobną bibliotekę Draw, podczas gdy biblioteka Core zawiera tylko podstawowe reprezentacje. Jest kilka możliwości, o których mogę myśleć:

1 - A, DrawManagerktóry bierze listę Itemsi i musi użyć, dynamic_cast<>aby dowiedzieć się, co zrobić:

class DrawManager {
  void draw(ItemList& items) {
    FOREACH(Item* item, items) {
       if (dynamic_cast<Rectangle*>(item)) {
          drawRectangle();
       } else if (dynamic_cast<Circle*>(item)) {
          drawCircle();
       } ....
    }
  }
};

Nie jest to idealne, ponieważ opiera się na RTTI i zmusza jedną klasę do bycia świadomym wszystkich pozycji w hierarchii.

2 - Drugim podejściem jest odłożenie odpowiedzialności na ItemDrawerhierarchię ( RectangleDraweritp.):

class Item {
   virtual Drawer* GetDrawer() =0;
}

class Rectangle : public Item {
public:
   virtual Drawer* GetDrawer() {return new RectangleDrawer(this); }
}

Uzyskuje się to rozdzielenie obaw między podstawową reprezentacją przedmiotów a kodem do rysowania. Problem polega jednak na tym, że klasy przedmiotów są zależne od klas rysunku.

Jak mogę oddzielić ten kod rysunkowy do osobnej biblioteki? Czy rozwiązaniem jest zwrócenie Przedmiotów do klasy fabrycznej określonego opisu? Jak można to jednak zdefiniować, aby biblioteka Core nie była zależna od biblioteki Draw?

the_mandrill
źródło

Odpowiedzi:

3

Spójrz na wzór odwiedzających .

Jego celem jest oddzielenie algorytmu (w twoim przypadku rysowania) od struktury obiektu, na której działa (elementy).

Prostym przykładem twojego problemu byłoby:

class Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Rectangle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};

class Circle : public Item
{
public:
    virtual void visit(Visitable v)
    {
        v.accept(*this)
    }
};


class Visitable
{
public:
    virtual void accept(Rectangle& r);
    virtual void accept(Circle& c);
};

class Drawer : public Visitable
{
public:
    void accept(Rectangle& r)
    {
        // draw rectangle
    }   
    void accept(Circle& c)
    {
        // draw circle
    }
};


int main()
{
    Item* i1 = new Circle;
    Item* i2 = new Rectangle;

    Drawer d;
    i1->visit(d); // Draw circle
    i2->visit(d); // Draw rectangle

    return 1;
}
gorący ziemniak
źródło
Ciekawe - nigdy nie myślałem o użyciu gościa z przeciążeniem do osiągnięcia polimorfizmu. Czy jednak to przeciążenie działałoby poprawnie w czasie wykonywania?
the_mandrill
Tak, przeciążałoby się poprawnie. Zobacz zredagowaną odpowiedź
hotpotato
Widzę, że to działa. Szkoda, że ​​każda klasa pochodna musi zaimplementować visit()metodę.
the_mandrill
To skłoniło mnie do trochę więcej poszukiwań i teraz znalazłem implementację wzorca Odwiedzającego Lokiego, który wydaje się być dokładnie tym, czego szukam! Znałem schemat odwiedzin w zakresie odwiedzania węzłów na drzewach, ale nie myślałem o tym w bardziej abstrakcyjny sposób.
the_mandrill
2

Podział, który opisujesz - na dwie równoległe hierarchie - jest zasadniczo wzorem mostu .

Martwisz się, że uzależnia to wyrafinowaną abstrakcję (Prostokąt itp.) Od implementacji (RectangleDrawer), ale nie jestem pewien, jak możesz tego całkowicie uniknąć.

Z pewnością możesz dostarczyć abstrakcyjną fabrykę, aby przełamać tę zależność, ale nadal potrzebujesz sposobu, aby powiedzieć fabryce, którą podklasę utworzyć, a ta podklasa nadal zależy od konkretnej dopracowanej implementacji. Oznacza to, że nawet jeśli Rectangle nie zależy od RectangleDrawer, nadal potrzebuje sposobu, aby poinformować fabrykę o jego zbudowaniu, a sam RectangleDrawer nadal zależy od Rectangle.

Warto również zapytać, czy jest to przydatna abstrakcja. Czy rysowanie prostokąta jest tak trudne, że warto umieścić go w innej klasie, czy naprawdę próbujesz wyodrębnić bibliotekę graficzną?

Nieprzydatny
źródło
Wydawało mi się, że widziałem już ten schemat posiadania równoległych hierarchii - dziękuję, że o tym pomyślałem. Chcę jednak wyodrębnić bibliotekę graficzną. Nie chcę, aby podstawowa biblioteka musiała zależeć od biblioteki graficznej. Powiedzmy, że podstawowa aplikacja zarządza obiektami kształtów, a biblioteka do ich wyświetlania jest dodatkiem. Pamiętaj jednak, że tak naprawdę nie jest to aplikacja do rysowania prostokątów - po prostu używam jej jako metafory.
the_mandrill
Co mam na myśli to, że RectangleDrawer, CircleDraweritd. Nie może być najbardziej przydatny sposób abstrakcyjny biblioteki graficznej. Jeśli zamiast tego odsłonisz zestaw prymitywów za pośrednictwem zwykłego abstrakcyjnego interfejsu, nadal przełamujesz zależność.
Bezużyteczne
1

Zobacz Semantykę Wartości i polimorfizm oparty na pojęciach .

Ta praca zmieniła moje spojrzenie na polimorfizm. Korzystaliśmy z tego systemu, aby mieć duży wpływ na powtarzające się sytuacje.

Bill Door
źródło
Wygląda to fascynująco - wydaje się, że zmierza w kierunku, w którym chcę
the_mandrill
1
Link jest zepsuty. Dlatego odradzane są odpowiedzi, które zasadniczo są tylko linkami.
ajb
0

Powodem, dla którego masz problem, jest to, że obiekty nie powinny się rysować. Prawidłowe narysowanie czegoś zależy od miliarda kawałków stanu, a prostokąt nie będzie miał żadnego z tych kawałków. Powinieneś mieć centralny obiekt graficzny, który reprezentuje stan rdzenia, taki jak backbuffer / bufor głębokości / shadery / etc, a następnie dać mu metodę Draw ().

DeadMG
źródło
Zgadzam się, że przedmioty nie powinny się rysować, stąd chęć przeniesienia tej umiejętności do innej klasy, aby osiągnąć rozdzielenie obaw. Załóżmy, że klasy te były Doglub Cat- obiekt odpowiedzialny za ich narysowanie jest o wiele bardziej złożony niż za narysowanie prostokąta.
the_mandrill