Design patterns 使用访问者设计模式的一个很好的理由?
在你告诉我已经有一个类似的问题之前,是的,我知道,我已经读过了。 但问题集中在何时,我感兴趣的是为什么 我知道事情是如何运作的。经典的动物、狗、猫的例子总是很有魅力 问题是这个代码Design patterns 使用访问者设计模式的一个很好的理由?,design-patterns,visitor-pattern,Design Patterns,Visitor Pattern,在你告诉我已经有一个类似的问题之前,是的,我知道,我已经读过了。 但问题集中在何时,我感兴趣的是为什么 我知道事情是如何运作的。经典的动物、狗、猫的例子总是很有魅力 问题是这个代码 int main() { Cat c; Sound theSound; c.letsDo(&theSound); } 我觉得很不自然。为什么? 我的意思是,是的,这样我就没有区分我的狗和猫模型(我第一次在英语中使用这个词,顺便说一句),因为真正的实现隐藏在Sound类下,但这不只是一
int main()
{
Cat c;
Sound theSound;
c.letsDo(&theSound);
}
我觉得很不自然。为什么?
我的意思是,是的,这样我就没有区分我的狗和猫模型(我第一次在英语中使用这个词,顺便说一句),因为真正的实现隐藏在Sound类下,但这不只是一种减轻代码负担的方法吗?多态性还不够做这样的事情吗
对我来说,不同之处在于,对于多态性,您必须编辑每个类(但模型保持不变,对吗?),而对于访问者设计模式,您只需编辑一个类。访问者模式允许您做一些事情,而仅仅依赖多态性是无法做到的:处理意外用例。如果您正在编写一个库,这是一个重要的问题。让我详细说明: 考虑一个使用访问者模式的经典示例,即对某棵树的节点的操作。要添加一些细节,比如说,您刚刚为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;
...
}
}
访问者模式将这个负担从客户端转移到了库中。假设您在一个不属于自己的库中定义了一些基本内容,您需要扩展它。比如:
// In base lib:
interface ISomething {
void DoSomething();
}
class Something1 : ISomething {
// ...
}
class Something2 : ISomething {
// ...
}
多态性允许您定义可以对其执行操作的新内容:
// In your lib:
class MySomething : ISomething {
}
现在,基本库可以处理您的MySomething
,就好像它已经定义了它一样。它不允许您添加新操作DoSomething
是我们唯一能用ISomething
做的事情。访问者模式解决了这个问题
缺点是,使用访问者模式会消耗您定义新类型的能力,就像我们刚才展示的那样。大多数语言允许您轻松地添加操作或类型,但不能同时添加操作或类型,这一事实称为
visitor模式很酷,但除了实现编译器之外,我从未发现有必要使用它。当我有一个对象树并需要以多种方式打印内容时,我使用了visitor模式。逗号sep、XML等等。我没有为每个输出格式添加新的print方法,而是使用visitor模式创建了CommaSepVisitor、XMLVisitor和HTMLVisitor类。树代码从未改变,因为我添加了更多的访问者类型,所以我从未引入bug。访问者本身很容易编写。访问者模式非常有用 使用它至少有三大原因:
干杯没错,该行为可以由第三方实现。好的,但是如果您定义了SQL解析器,您还没有语法定义吗?意外的用例在哪里?实际上,既然我们讨论的是解析,那么解析器生成器不应该是一个更合适的例子吗?这里你有一个任意的语法,所以你必须定义一个通用的树行者类,它不是关于SQL的结构。这是关于客户端如何使用库的结果/输出。之所以使用解析器示例,是因为它有点“经典”。顺便说一句,如果您有一个没有固定结构的完全通用API(任意“DOM”类节点),那么最好使用通用访问器方法,而不是访问者模式。您不能添加新类型,因为访问者仍然是库的一个组件,对吗?visitor类将在基本库中定义,并且将有一组固定的方法,每种类型一个。您不能在库中添加新类型,因为您不能更改访问者,也不能将访问者移动到库中,因为基本库中的类型需要在其
accept()
方法中引用它。