C++:对访客模式的怀疑

发布于 2024-10-02 13:00:18 字数 1043 浏览 4 评论 0原文

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


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

我经常需要向某些类添加功能,但不将这些新功能添加到库中。让我用一个真实的例子:

在这个库中,我有一个类 Shape,由 CircleShapePolygonShapeCompositeShape< 继承。 /代码>。

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

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

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

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

使用 dynamic_cast 显式向上转换 Shape 成本更高,但对我来说,它看起来是一个更干净的解决方案。


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

I know what Visitor Pattern is and how to use it; this question is not a duplicate of this one.


I've got a library where I put most of the reusable code I write, and which I link to most of my projects.

Often I need to add features to some classes, but without adding these new features to the library. Let me use a real example:

In this lib I've got a class Shape, inherited by CircleShape, PolygonShape and CompositeShape.

I'm now developing a graphical application where I need to render these Shape, but don't want to put a virtual function render in the core Shape class, since some of my projects which use Shape don't do any rendering, and other graphical projects could use different rendering engines (I'm using Qt for this project, but for a game I'd use OpenGL, thus the render function will need different implementations).

The most famous way to do this is using Visitor Pattern, of course, but this pops a few doubts into my mind:

Any class of any library could need to be extended as my Shape does. Most of the public libraries (about all of them) don't provide any support for Visitor Pattern; why? why should I?

Visitor Pattern is a way to simulate Double Dispatching in C++. It's not native in C++, and requires to be explicitly implemented, making the class interface more complex: I don't think the applyVisitor function should be at the same level of my class' functions, I see this like breaking abstraction.

Explicitly up-casting Shape with dynamic_cast is more expensive, but to me it looks like a cleaner solution.


So, what should I do? Implementing Double Dispatching in all my library classes? What if the library providing Shape wasn't mine, but some GPL library found on the internet?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

半边脸i 2024-10-09 13:00:18

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


类层次结构上的操作是通过添加虚函数还是通过添加访问者来实现由概率决定添加类与添加操作的比较:

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

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

然而,对于第三方库还有另一个方面:通常,用户无法更改库中的类。也就是说,如果他们需要添加操作,他们执行此操作的唯一方法是添加访问者 - 如果库提供了挂钩让他们插入其中
因此,如果您正在编写一个库,并且认为用户应该能够向其中添加操作,那么您需要提供一种方法,让他们将访问者插入您的库

First: "Visitor Pattern is a way to simulate Double Dispatching in C++." This is, erm, not fully right. Actually, double dispatch is one form of multiple dispatch, which is a way to simulate (the missing) multi-methods in C++.


Whether operations on a class hierarchy should be implemented by adding virtual functions or by adding visitors is determined by the probabilities of adding classes vs. adding operations:

  • If the number of classes keeps changing more rapidly than the number of operations, use virtual functions. That is because adding a class requires modifying all visitors.
  • If the number of classes is relatively stable compared to the number of operations, use visitors. That is because adding a virtual function requires changing all classes in the hierarchy.

Yes, many libraries do not come with a visitor interface.
When we are just looking at the reasoning above, this would be right if the number of classes changes often. That is, if a library is released often, with new classes being added constantly, then providing a visitor interface wouldn't make much sense, because each time a new release brings new classes, everybody using the library need to adapt all of their visitors. So if we only looked at the above reasoning, a visitor interface would seem to only be helpful if the number of classes in a lib's class hierarchy seldom or never changes.

However, with 3rd-party libraries there's another aspect to that: Usually, users cannot change the classes in the library. That is, if they need to add an operation, the only way they can do this is by adding a visitor - if the library provides the hooks for them to plug into it.
So if you are writing a library and feel like users should be able to add operations to it, then you need to provide a way for them to plug their visitors into your lib.

寻找一个思念的角度 2024-10-09 13:00:18

对我来说,这看起来不像访客模式的情况。

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

如果要支持多个渲染引擎,可以使用 RenderContext 基类来抽象绘图操作,并为每个渲染引擎创建子类,每个子类根据其渲染引擎实现绘图操作。然后,您可以让 RenderableShape::renderRenderContext 作为参数,并使用其抽象 API 进行绘制。

This doesn't look like a case for the Visitor pattern to me.

I would suggest you have a RenderableShape class that aggregates a Shape object, then create subclasses for each shape. RenderableShape would have a virtual render method.

If you want to support multiple rendering engines, you can have a RenderContext base class that abstracts drawing operations, with subclasses for each rendering engine, each subclass implementing the drawing operations in terms of its rendering engine. You then have RenderableShape::render take a RenderContext as an argument, and draw to it using its abstracted API.

雄赳赳气昂昂 2024-10-09 13:00:18

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

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

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

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

 CircleRenderer hasA Cicle, knows how to render Circles

等等。现在在渲染器类中使用访问者模式。

So there's a class xxxShape, which in some way contains information that "drives" the rendering. For Circles that might be centre, radius, for Squares some corner coordinates or some such. Maybe some other stuff about fillings and colours.

You dont't want to/cannot update those classes to add the actual rendering logic, and I think your reasons for not doing so are valid/inevitable.

But presumably, you have enough public access methods on the classes to allow you to get at the "driving" information, otherwise you are doomed.

So in which case why can you not just wrap these items:

 CircleRenderer hasA Cicle, knows how to render Circles

and so on. Now use the Visitor pattern across the Renderer classes.

农村范ル 2024-10-09 13:00:18

有许多可能的解决方案,但您可以这样做,例如:启动新的层次结构,在特定的上下文中呈现形状

// 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) {}
};

There a many possible solutions, but you could do this, for example: Start new hierarchy, which renders Shapes in a specific 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) {}
};
半葬歌 2024-10-09 13:00:18

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

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

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

如果有机会,请看一下 一篇文章我已经写过相关内容

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

我希望它有帮助

干杯

I understand absolutely what you said and I share the same concerns.
The problem is that the Visitor Pattern is not very clearly defined and the original solution for it is misleading, IMHO. This is why there are so many variations of this pattern.

In particular, I believe that the correct implementation should support legacy code, I mean: a binary you've lost the source code at all, isn't it? This is what the definition says: that you should never had to change the original data structures.

I don't like implementations with visitA, visitB, visitWhatever, acceptA, acceptB, acceptWhatever. This is absolutely wrong, IMHO.

If you have a chance, please have a look at an article I've written about this.

It's Java, but you can port to C++ easily if you find it useful for your purposes.

I hope it helps

Cheers

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文