Шаблон проектирования для полиморфного поведения при разрешении разделения библиотеки

10

Скажем , у меня есть иерархия Itemклассов: Rectangle, Circle, Triangle. Я хочу иметь возможность рисовать их, поэтому моя первая возможность - добавить виртуальный Draw()метод к каждому:

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

Однако я хочу разделить функциональность рисования на отдельную библиотеку Draw, в то время как библиотека Core содержит только основные представления. Есть несколько возможностей, о которых я могу подумать:

1 - A, DrawManagerкоторый берет список Items и должен использовать, dynamic_cast<>чтобы решить, что делать:

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

Это не идеально, так как он опирается на RTTI и заставляет один класс быть в курсе всех элементов в иерархии.

2 - Другой подход - отложить привлечение ответственности к ItemDrawerиерархии ( RectangleDrawerи т. Д.):

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

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

Это позволяет разделить проблемы между базовым представлением Предметов и кодом для рисования. Проблема, однако, в том, что классы Item зависят от классов рисования.

Как я могу выделить этот код для рисования в отдельную библиотеку? Является ли решение для Предметов вернуть фабричный класс некоторого описания? Однако как это можно определить, чтобы библиотека Core не зависела от библиотеки Draw?

the_mandrill
источник

Ответы:

3

Посмотрите на шаблон посетителя .

Он предназначен для отделения алгоритма (в вашем случае рисования) от структуры объекта, над которой он работает (элементы).

Таким простым примером для вашей проблемы будет:

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;
}
горячая картошка
источник
Интересно - я никогда не думал об использовании посетителя с перегрузкой для достижения полиморфизма. Будет ли эта перегрузка правильно во время выполнения, хотя?
the_mandrill
Да, это было бы правильно перегрузить. Смотрите отредактированный ответ
hotpotato
Я вижу, что это работает. Обидно, что каждый производный класс должен реализовывать visit()метод.
the_mandrill
Это побудило меня сделать немного больше поиска, и теперь я нашел реализацию шаблона посетителя Локи, который, кажется, именно то, что я ищу! Я был знаком с моделью посетителя с точки зрения посещения узлов на деревьях, но не думал об этом более абстрактно.
the_mandrill
2

Разделение, которое вы описываете - на две параллельные иерархии - по сути является образцом моста .

Вы волнуетесь, что это делает уточненную абстракцию (Rectangle и т. Д.) Зависимой от реализации (RectangleDrawer), но я не уверен, как вы могли бы избежать этого полностью.

Вы, конечно, можете предоставить абстрактную фабрику, чтобы разорвать эту зависимость, но вам все еще нужен какой-то способ сообщить фабрике, какой подкласс создать, и этот подкласс все еще зависит от конкретной уточненной реализации. То есть, даже если Rectangle не зависит от RectangleDrawer, ему все равно нужен способ сообщить фабрике о его создании, а сам RectangleDrawer все еще зависит от Rectangle.

Также стоит спросить, является ли это полезной абстракцией. Рисование прямоугольника настолько сложно, что стоит добавить другой класс, или вы действительно пытаетесь абстрагировать графическую библиотеку?

Бесполезный
источник
Я думал, что видел этот шаблон до того, как имел параллельные иерархии - спасибо, что пробежался по этому поводу. Хотя я хочу абстрагировать графическую библиотеку. Я не хочу, чтобы основная библиотека зависела от графической библиотеки. Допустим, основное приложение управляет объектами формы, а библиотека для их отображения является надстройкой. Имейте в виду, что это не приложение для рисования прямоугольников - я просто использую это как метафору.
the_mandrill
Я думаю, что RectangleDrawerи CircleDrawerт. Д., Возможно, не самый полезный способ абстрагирования графической библиотеки. Если вместо этого вы предоставляете набор примитивов через обычный абстрактный интерфейс, вы все равно нарушаете зависимость.
бесполезно
1

Взгляните на семантику значений и основанный на понятиях полиморфизм .

Эта работа изменила мой взгляд на полиморфизм. Мы использовали эту систему, чтобы сильно повлиять на повторяющиеся ситуации.

Билл Дверь
источник
Это выглядит увлекательно - кажется, что оно движется в
нужном
1
Ссылка не работает. Вот почему ответы, которые по сути только для ссылок, не приветствуются.
AJB
0

Причина возникновения проблемы в том, что объекты не должны рисовать сами. Правильное рисование чего-либо зависит от миллиарда кусочков состояния, и прямоугольник не будет иметь ни одного из этих кусочков. У вас должен быть центральный графический объект, который представляет состояние ядра, такой как буфер буферов / буфер глубины / шейдеры / и т. Д., А затем назначьте ему метод Draw ().

DeadMG
источник
Я согласен с тем, что объекты не должны рисовать самих себя, поэтому возникает желание перенести эту способность в другой класс для достижения разделения интересов. Предположим, что эти классы были Dogили Cat- объект, ответственный за их рисование, намного сложнее, чем за рисование прямоугольника.
the_mandrill