介绍
显然,我的整个程序员生涯都在做一个“非正统”的访问者模式。
是的,我从访问者的方法中分派到一个具体的复合元素访问Visit
方法。
我想这就是我学习它的方式,但现在我找不到任何例子,我学习它的来源也不见了。
现在,面对压倒性的证据表明具体元素调度进入了复合元素的Accept
方法,我想知道我一直在做的方式是否至少有一些优势。在我看来,两个优点是:
- 我只有一个地方可以决定如何调度:基础访客。
- 我可以添加新的复合元素类型,并让基本访问者忽略它们,但派生访问者可以覆盖
Visit
以处理它们。
例子
这是基本的复合/访客模型:
// "Unorthodox" version
public class BaseVisitor
{
public virtual void Visit(CompositeElement e)
{
if(e is Foo)
{
VisitFoo((Foo)e);
}
else if(e is Bar)
{
VisitBar((Bar)e);
}
else
{
VisitUnknown(e);
}
}
protected virtual void VisitFoo(Foo foo) { }
protected virtual void VisitBar(Bar bar) { }
protected virtual void VisitUnknown(CompositeElement e) { }
}
public class CompositeElement
{
public virtual void Accept(BaseVisitor visitor) { }
}
public class Foo : CompositeElement { }
public class Bar : CompositeElement { }
请注意,访问者类现在负责第二个基于类型的调度,而不是规范版本,例如,Foo
将负责它并且将具有:
// Canonical visitor pattern 2nd dispatch
public override void Accept(BaseVisitor visitor)
{
visitor.VisitFoo(this);
}
现在,为了防守...
优势一
假设我们要添加一个新的 CompositeElement 类型:
public class Baz : CompositeElement { }
为了在访问者模型中适应这种新元素类型,我只需要对 BaseVisitor 类进行更改:
public class BaseVisitor
{
public virtual void Visit(CompositeElement e)
{
// Existing cases elided...
else if(e is Baz)
{
VisitBaz((Baz)e);
}
}
protected virtual void VisitBaz(Foo foo) { }
}
诚然,这是一个小问题,但它似乎确实简化了维护(也就是说,如果您不介意大if
或switch
声明)。
优势二
假设我们想在一个单独的包中扩展组合。我们可以在不修改的情况下适应这一点BaseVisitor
:
public class ExtendedVisitor : BaseVisitor
{
public override Visit(CompositeElement e)
{
if(e is ExtendedElement)
{
VisitExtended((ExtendedElement)e);
}
else
{
base.Visit(e);
}
}
protected virtual void VisitExtended(ExtendedElement e) { }
}
public class ExtendedCompositeElement : CompositeElement { }
拥有这种结构使我们能够打破为了容纳扩展的 CompositeElement 类型而BaseVisitor
需要拥有的依赖关系。VisitExtended
结论
在这一点上,我还没有实现足够长的访问者模式或维护它足够长的时间,以使任何不利因素对我造成影响。显然,维护一个大的 switch 语句是一种痛苦,并且会影响性能,但是我不确定它们是否超过了保持BaseVisitor
对扩展的依赖的灵活性。
请权衡您对不利因素的看法。