5

在你告诉我已经有一个类似的问题之前,是的,我知道,我已经读过。但是那里的问题集中在什么时候,我对为什么感兴趣。

我明白事情是如何运作的。经典的动物、狗、猫的例子总是很有魅力。

问题是这段代码

int main()
{
    Cat c;
    Sound theSound;
    c.letsDo(&theSound);
}

对我来说似乎很不自然。为什么?

我的意思是,是的,这样我的 Dog 和 Cat 模型就没有区别了(顺便说一句,我第一次在英语中使用这个词),因为真正的实现隐藏在 Sound 类下,但这不仅仅是一种压低代码的方法? 多态性还不足以做这样的事情吗?

对我而言,不同之处在于,使用多态性,您必须编辑每个类(但模型保持不变,对吗?),而您只需使用访问者设计模式编辑一个类。

4

4 回答 4

8

访问者模式允许您做一些简单地依赖多态性无法做到的事情:处理未预料到的用例。如果您正在编写库,这是很重要的一点。让我详细说明:

考虑一个使用访问者模式的经典例子,即对某个抽象语法树的节点的操作。要添加一些细节,比如说,您刚刚为 SQL 编写了一个解析器库,它接受字符串,解析它们,并为它在输入中找到的内容返回一个 AST。除非您可以预测客户端代码可能针对此类 AST 的所有潜在用例,否则您必须提供一种“通用”的方式来遍历 AST。提供类似 DOM 的访问器函数 ( getNodeType, getParentNode, getPreviousNode) 是一种方法。这里的问题是,这给图书馆的客户带来了沉重的负担,因为他们需要自己进行调度。更重要的是,他们需要非常详细地了解每种可能的节点类型要遵循哪些指针:

void 
walk_tree(AstNode* node) 
{
    switch( node->getNodeType() ) {
    case SELECT_NODE:
        for( AstNode* child = node->getFirstChild(); child; child = child->getNextNode() ) {
             walk_tree(child);
        }
        break;
    ...
    }
}

访问者模式将这种负担从客户端转移到库中。

于 2010-09-10T15:08:52.643 回答
3

假设您在不属于您的库中定义了一些基本的东西,您需要扩展它。像:

// In base lib:
interface ISomething {
    void DoSomething();
}

class Something1 : ISomething {
    // ...
}

class Something2 : ISomething {
    // ...
}

多态性使您可以定义可以对其执行操作的新事物:

// In your lib:
class MySomething : ISomething {
}

现在基本库可以与您一起使用MySomething,就好像它已经定义了它一样。它不允许您做的是添加新操作DoSomething是我们唯一能用ISomething. 访问者模式解决了这个问题。

缺点是使用访问者模式会使您失去定义新类型的能力,就像我们刚刚展示的那样。大多数语言允许您轻松添加操作或类型,但不能同时添加,这一事实称为表达式问题

访问者模式是一个很酷的模式,但除了实现编译器之外,我从未真正发现需要它。

于 2010-09-13T01:06:26.197 回答
2

访问者模式非常有用。

使用它至少有三大原因:

  1. 减少仅在数据结构发生变化时略有不同的代码的扩散。

  2. 将相同的计算应用于多个数据结构,而无需更改实现计算的代码。

  3. 在不更改遗留代码的情况下向遗留库添加信息。

请看一下我写的一篇关于这个的文章

干杯

于 2011-01-29T23:09:50.527 回答
1

当我有一个对象树并且需要以多种方式打印内容时,我使用了访问者模式。逗号分隔符、XML 等等。我没有为每个输出格式添加新的打印方法到树类中,而是使用访问者模式并创建了 CommaSepVisitor、XMLVisitor 和 HTMLVisitor 类。随着我添加更多访问者类型,树代码从未改变,因此我从未引入错误。来访者自己很容易写作。

于 2010-10-16T21:37:29.347 回答