我知道访客模式是什么以及如何使用它;这个问题与此one不重复。
我有一个库,我把大部分可重复使用的代码放在我的大部分项目中。
我经常需要为某些类添加功能,但不会将这些新功能添加到库中。让我用一个真实的例子:
在这个lib中,我有一个由Shape
,CircleShape
和PolygonShape
继承的课程CompositeShape
。
我现在正在开发一个图形应用程序,我需要渲染这些Shape
,但不想在核心render
类中放置虚函数Shape
,因为有些我使用Shape
的项目不进行任何渲染,其他图形项目可以使用不同的渲染引擎(我在这个项目中使用Qt,但对于一个我使用OpenGL的游戏,因此{{ 1}}函数需要不同的实现。)
最着名的方法是使用访客模式,当然,这会让我产生一些怀疑:
任何库的任何类都需要像我的render
一样进行扩展。大多数公共图书馆(大约所有公共图书馆)都没有为访客模式提供任何支持;为什么?我为什么要这样做?
访问者模式是一种在C ++中模拟Double Dispatching的方法。它在C ++中不是原生的,需要显式实现,使得类接口更复杂:我不认为Shape
函数应该与我的类函数处于同一级别,我认为这就像打破抽象
使用applyVisitor
明确上传Shape
的费用更高,但对我而言,这看起来更清晰。
那么,我该怎么办?在我的所有库类中实现Double Dispatching?如果提供dynamic_cast
的图书馆不是我的,但是在互联网上找到了一些GPL图书馆怎么办?
答案 0 :(得分:14)
首先:“访问者模式是一种在C ++中模拟Double Dispatching的方法。”这是,嗯,不完全正确。实际上,双调度是多调度的一种形式,这是一种在C ++中模拟(缺失)多方法的方法。
是否应通过 添加虚拟功能 或 添加访问者 来实施类层次结构上的操作通过添加类与添加操作的概率:
是的,许多图书馆没有访问者界面 当我们只看上面的推理时,如果类的数量经常变化,那就是正确的。也就是说,如果经常发布库,并且不断添加新类,那么提供访问者界面就没有多大意义,因为每次新版本带来新类时,使用该库的每个人都需要调整所有访问者。因此,如果我们只看上面的推理,那么如果lib的类层次结构中的类数很少或永远不会改变,那么访问者界面似乎才有用。
但是,对于第三方库,还有另一个方面:通常,用户无法更改库中的类。也就是说,如果他们需要添加操作, 他们可以执行此操作的唯一方法是添加访问者 - 如果库提供了挂钩让他们插入 因此,如果您正在编写库并且感觉用户应该能够向其添加操作,那么 您需要为他们提供一种方式将访问者插入您的lib 。
答案 1 :(得分:0)
对于我而言,这看起来不像访客模式。
我建议您有一个RenderableShape
类聚合Shape
对象,然后为每个形状创建子类。 RenderableShape
将采用虚拟render
方法。
如果要支持多个渲染引擎,可以使用RenderContext
基类抽象绘制操作,每个渲染引擎都有子类,每个子类根据渲染引擎实现绘制操作。然后,您RenderableShape::render
将RenderContext
作为参数,并使用其抽象的API绘制它。
答案 2 :(得分:0)
所以有一个类xxxShape,它在某种程度上包含“驱动”渲染的信息。对于可能是中心的圆,半径,对于正方形的某些角坐标或某些角坐标。也许其他一些关于填充物和颜色的东西。
您不希望/无法更新这些类以添加实际的渲染逻辑,我认为您不这样做的原因是有效/不可避免的。
但是大概,你在课程上有足够的公共访问方法,可以让你获得“驾驶”信息,否则你就注定了。
所以在这种情况下为什么你不能只包装这些项目:
CircleRenderer hasA Cicle, knows how to render Circles
等等。现在在Renderer类中使用Visitor模式。
答案 3 :(得分: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) {}
};
答案 4 :(得分:0)
我完全理解你说的话,我也有同样的担忧。 问题是访客模式的定义不是很清楚,原来的解决方案是误导性的,恕我直言。这就是为什么这种模式有很多变化的原因。
特别是,我认为正确的实现应该支持遗留代码,我的意思是:你完全丢失了源代码的二进制文件,不是吗?这就是定义所说的:您永远不必更改原始数据结构。
我不喜欢visitA,visitB,visitWhatever,acceptA,acceptB,acceptWhatever的实现。这绝对是错的,恕我直言。
如果有机会,请查看an article I've written about this。
它是Java,但如果您发现它对您的目的有用,则可以轻松移植到C ++。
我希望它有所帮助
干杯