代码之家  ›  专栏  ›  技术社区  ›  peoro

对访问者模式的质疑

  •  9
  • peoro  · 技术社区  · 14 年前

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


    我有一个库,我把我写的大部分可重用代码放在那里,并链接到我的大多数项目。

    通常我需要向一些类中添加特性,但不需要向库中添加这些新特性。让我用一个真正的例子:

    在这个自由中我有一个班 Shape ,继承者 CircleShape , PolygonShape CompositeShape

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

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

    任何一个图书馆的任何类都需要作为我的 形状 做。大多数公共图书馆(几乎所有的图书馆)都不支持访客模式;为什么?我为什么要?

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

    明确向上铸造 形状 具有 dynamic_cast 更贵,但对我来说,它看起来更清洁。


    那么,我该怎么办?在我所有的库类中实现双重调度?如果图书馆提供 形状 不是我的,但在互联网上找到了一些GPL库?

    5 回复  |  直到 14 年前
        1
  •  14
  •   sbi    14 年前

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


    类层次结构上的操作是否应由 添加虚拟函数 或由 添加访问者 由添加类与添加操作的概率决定:

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

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

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

        2
  •  0
  •   Martin Broadhurst    14 年前

    在我看来,这不像是访客模式的例子。

    我建议你有一个 RenderableShape 聚合a的类 Shape 对象,然后为每个形状创建子类。 伦德拉布勒沙佩 会有一个虚拟的 render 方法。

    如果要支持多个渲染引擎,可以使用 RenderContext 抽象绘图操作的基类,每个渲染引擎都有子类,每个子类根据其渲染引擎实现绘图操作。你就有了 RenderableShape::render 拿一个 呈现上下文 作为参数,并使用其抽象的API进行绘制。

        3
  •  0
  •   djna    14 年前

    所以有一个类XXXShape,它在某种程度上包含“驱动”渲染的信息。对于圆心,半径,对于正方形,一些角坐标或者类似的。也许还有一些关于填充物和颜色的东西。

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

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

    所以在这种情况下,你为什么不能把这些东西包装起来呢?

     CircleRenderer hasA Cicle, knows how to render Circles
    

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

        4
  •  0
  •   Paul Michalik    14 年前

    有许多可能的解决方案,但您可以这样做,例如:启动新的层次结构,它将 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) {}
    };
    
        5
  •  0
  •   Richard Gomes    14 年前

    我完全理解你所说的,我也有同样的担心。 问题是,访客模式没有很清楚地定义,而且最初的解决方案是误导性的,imho。这就是为什么这种模式有如此多的变化。

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

    我不喜欢使用visita、visitb、visitwhatever、accept、acceptb、acceptwhatever实现。这绝对是错误的,imho。

    如果你有机会,请看一下 an article I've written about this .

    它是Java,但是如果你发现它对你的目的有用,你可以很容易地将它移植到C++。

    希望有帮助

    干杯