9

我知道访客模式是什么以及如何使用它;这个问题不是这个问题的重复


我有一个库,其中放置了我编写的大部分可重用代码,并链接到我的大多数项目。

通常我需要向某些类添加特性,但没有将这些新特性添加到库中。让我用一个真实的例子:

在这个库中,我有一个类,由和Shape继承。CircleShapePolygonShapeCompositeShape

我现在正在开发一个图形应用程序,我需要在其中渲染这些Shape,但不想render在核心Shape类中放置一个虚函数,因为我使用的一些项目Shape不进行任何渲染,而其他图形项目可以使用不同的渲染引擎(我在这个项目中使用 Qt,但对于游戏我会使用 OpenGL,因此该render函数需要不同的实现)。

当然,最有名的方法是使用访问者模式,但这让我产生了一些疑问:

任何库的任何类都可能需要像我Shape一样进行扩展。大多数公共图书馆(几乎全部)都不提供对访问者模式的任何支持;为什么?我为什么要?

访问者模式是一种在 C++ 中模拟双重调度的方法。它在 C++ 中不是原生的,需要显式实现,这使得类接口更加复杂:我认为该applyVisitor函数不应该与我的类函数处于同一级别,我认为这就像破坏抽象。

显式向上转换Shapedynamic_cast昂贵,但对我来说它看起来像一个更清洁的解决方案。


所以我该怎么做?在我所有的库类中实现双重调度?如果提供的库Shape不是我的,而是在互联网上找到的一些 GPL 库怎么办?

4

5 回答 5

15

第一:“访问者模式是一种在 C++ 中模拟双重调度的方法。” 这是,呃,不完全正确。实际上,双重分派是多重分派的一种形式,它是在 C++ 中模拟(缺失的)多方法的一种方式。


类层次结构上的操作是否应该通过添加虚函数添加访问者来实现,取决于添加类与添加操作的概率:

  • 如果类的数量比操作的数量变化更快,请使用虚函数。那是因为添加一个类需要修改所有访问者。
  • 如果类的数量与操作的数量相比相对稳定,请使用 visitor。这是因为添加虚函数需要更改层次结构中的所有类。

是的,许多图书馆没有访客界面。
当我们只看上面的推理时,如果类的数量经常变化,这将是正确的。也就是说,如果一个库经常发布,并且不断添加新类,那么提供访问者界面就没有多大意义,因为每次新版本带来新类时,使用该库的每个人都需要调整所有访问者. 因此,如果我们只看上面的推理,访问者界面似乎只有在 lib 的类层次结构中的类数量很少或从不改变时才有用。

但是,对于 3rd-party 库,还有另一个方面:通常,用户无法更改库中的类。也就是说,如果他们需要添加一个操作,他们可以做到这一点的唯一方法是添加一个访问者——如果库为他们提供了插入它的钩子
因此,如果您正在编写一个库并且觉得用户应该能够向其中添加操作,那么您需要为他们提供一种将访问者插入您的库的方法

于 2010-11-14T13:25:22.900 回答
0

有许多可能的解决方案,但您可以这样做,例如:开始新的层次结构,它呈现Shapes在特定的Context

// contracts:

class RenderingContext {
public: virtual void DrawLine(const Point&, const Point&) = 0; 
    // and so on...
};

class ShapeRenderer {
public: virtual void Render(RenderingContext&) = 0;
};

// implementations:

class RectangleRenderer : public ShapeRenderer {
 Rectangle& mR;

public: 
 virtual void Render(RenderingContext& pContext) {
   pContext.DrawLine(mR.GetLeftLower(), mR.GetRightLower());
   // and so on...
 }

 RectangleRenderer(Rectangle& pR) : mR(pR) {}
};
于 2010-11-14T12:55:40.783 回答
0

我完全理解你所说的,我也有同样的担忧。问题是访问者模式的定义不是很明确,而且它的原始解决方案具有误导性,恕我直言。这就是为什么这种模式有如此多的变化。

特别是,我认为正确的实现应该支持遗留代码,我的意思是:一个二进制文件你已经完全丢失了源代码,不是吗?这就是定义所说的:您永远不必更改原始数据结构。

我不喜欢visitA、visitB、visitWhatever、acceptA、acceptB、acceptWhatever 的实现。这是绝对错误的,恕我直言。

如果你有机会,请看看我写的一篇关于这个的文章

它是 Java,但如果您发现它对您的目的有用,您可以轻松地移植到 C++。

我希望它有帮助

干杯

于 2011-01-29T23:30:34.340 回答
0

在我看来,这不像是访问者模式的情况。

我建议你有一个RenderableShape聚合Shape对象的类,然后为每个形状创建子类。RenderableShape会有一个虚render方法。

如果要支持多个渲染引擎,可以有一个RenderContext抽象绘图操作的基类,每个渲染引擎都有子类,每个子类根据其渲染引擎实现绘图操作。然后,您RenderableShape::render将 aRenderContext作为参数,并使用其抽象 API 绘制它。

于 2010-11-14T12:40:12.683 回答
0

所以有一个类 xxxShape,它以某种方式包含“驱动”渲染的信息。对于可能是中心的圆,半径,对于正方形,一些角坐标或一些这样的。也许还有其他一些关于填充物和颜色的东西。

您不想/不能更新这些类以添加实际的渲染逻辑,我认为您不这样做的原因是有效/不可避免的。

但是大概,您在类上有足够的公共访问方法来允许您获取“驾驶”信息,否则您注定要失败。

那么在这种情况下,为什么你不能只包装这些物品:

 CircleRenderer hasA Cicle, knows how to render Circles

等等。现在在 Renderer 类中使用访问者模式。

于 2010-11-14T12:41:41.157 回答