允许库分离的多态行为设计模式


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库?

Answers:


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 2013年

我可以看到这可行。每个派生类都必须实现该visit()方法,这是一个遗憾。
the_mandrill 2013年

这促使我进行了更多搜索,现在我发现了Loki的Visitor模式的实现,这似乎正是我想要的!我在访问树中的节点方面对访问者模式很熟悉,但是没有以更抽象的方式想到它。
the_mandrill

2

您描述的拆分-分为两个并行的层次结构-本质上是桥接模式

您担心它会使精炼的抽象(Rectangle等)依赖于实现(RectangleDrawer),但是我不确定如何完全避免这种情况。

您当然可以提供一个抽象工厂来打破这种依赖性,但是您仍然需要某种方法来告诉工厂要创建哪个子类,并且该子类仍然取决于特定的改进实现。也就是说,即使Rectangle不依赖于RectangleDrawer,它仍然需要一种方法来告诉工厂进行构建,而RectangleDrawer本身仍然依赖于Rectangle。

还值得询问这是否是有用的抽象。绘制矩形是如此困难,值得将其放入另一个类中,还是您真的在尝试抽象图形库?


我以为自己在拥有平行的层次结构之前就已经看到了这种模式-感谢您的投入。我虽然想要抽象图形库。我不希望核心库必须依赖于图形库。假设核心应用程序管理形状对象,用于显示形状对象的库是一个附加组件。请记住,尽管这实际上不是绘制矩形的应用程序-我只是将其用作隐喻。
the_mandrill

我在想的是RectangleDrawerCircleDrawer等等可能不是抽象图形库的最有用方法。相反,如果您通过常规的抽象接口公开一组基元,则仍然会破坏依赖关系。
没用的2013年


0

您遇到问题的原因是因为对象不应自己绘制。正确绘制某些东西取决于十亿个状态,而Rectangle不会包含任何这些状态。您应该有一个中央图形对象来表示核心状态,例如backbuffer / depth buffer / shaders / etc,然后为其提供Draw()方法。


我同意对象不应该吸引自己,因此希望将这种能力转移到另一个类中以实现关注点分离。假设这些类人DogCat-负责把他们拉了很多比绘制矩形更复杂的对象。
the_mandrill
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.