Шаблон дизайну для поліморфної поведінки, дозволяючи розділити бібліотеку


10

Скажімо , у мене є ієрархія Itemкласів: Rectangle, Circle, Triangle. Я хочу вміти їх малювати, тому моя перша можливість - додати віртуальний Draw()метод до кожного:

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

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

1 - Таблиця, 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?

Відповіді:


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

Розділений ви опис - на дві паралельні ієрархії - по суті є мостовим малюнком .

Ви хвилюєтесь, що це робить вишукану абстракцію (Прямокутник тощо) залежною від реалізації (RectangleDrawer), але я не впевнений, як ви могли цілком цього уникнути.

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

Також варто запитати, чи корисна це абстракція. Чи намалювати прямокутник настільки важко, що варто ввести його в інший клас, або ви справді намагаєтесь абстрагувати бібліотеку графіки?


Я думав, що бачив цю картину раніше, ніж мав паралельні ієрархії - дякую за те, що пробіг із цього приводу. Хоча я хочу абстрагувати бібліотеку графіки. Я не хочу, щоб основна бібліотека повинна залежати від графічної бібліотеки. Скажімо, що основний додаток управляє об’єктами форми, а бібліотека для їх відображення є додатком. Майте на увазі, що це дійсно не програма для малювання прямокутників - я просто використовую це як метафору.
the_mandrill

Те , що я маю в виду, що RectangleDrawer, і CircleDrawerт.д. , не може бути найбільш корисним способом абстрактної графічної бібліотеки. Якщо, замість цього, ви виставляєте набір примітивів через звичайний абстрактний інтерфейс, ви все одно порушуєте залежність.
Марно

1

Погляньте на поліморфізм, заснований на цінності та поняттях .

Ця робота змінила те, як я дивлюся на поліморфізм. Ми використовували цю систему, щоб мати великий вплив у повторних ситуаціях.


Це виглядає захоплююче - здається, що він прямує у напрямку, в якому я хочу
the_mandrill

1
Посилання розірвано. Ось чому відповіді, які по суті є лише посиланнями, не враховують.
айб

0

Причина, чому у вас виникають проблеми, полягає в тому, що об’єкти не повинні малювати себе. Намалювати щось правильно залежить від стану мільярда, і прямокутник не матиме жодної з цих частин. У вас повинен бути центральний графічний об'єкт, який представляє стан ядра, як резервний буфер / буфер глибини / шейдери / тощо, а потім надати йому метод Draw ().


Я погоджуюся, що об’єкти не повинні малювати себе, отже, бажання перенести цю здатність в інший клас, щоб досягти роз'єднання проблем. Припустимо, ці класи були Dogабо Cat- об'єкт, відповідальний за їх малювання, набагато складніший, ніж для малювання прямокутника.
the_mandrill
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.