C# 中更简洁的 Visitor 实现
Less verbose Visitor implementation in C#
假设您有一个抽象 BaseClass
和一些派生的 classes,并且您需要访问带有 Visitor
的 List<BaseClass>
。代码将是:
class Visitor
{
public void Visit(Derived1 visitable) { /*do something*/ }
public void Visit(Derived2 visitable) { /*do something*/ }
public void Visit(Base visitable) { thrown new InvalidOperationException(); }
//other derivatives
}
class Base
{
public virtual void Accept(Visitor visitor) { visitor.Visit(this); }
}
class Derived1 : Base
{
//override is mandatory to have Visit(Derived1 visitable) called
public override void Accept(Visitor visitor) { visitor.Visit(this); }
}
class Derived2 : Base
{
//override is mandatory to have Visit(Derived2 visitable) called
public override void Accept(Visitor visitor) { visitor.Visit(this); }
}
然后你就可以在如下方法中使用所有这些东西:
var baseClassList = new List<BaseClass>();
//fill baseClassList somehow
var visitor = new Visitor();
foreach(var visitable in baseClassList)
visitable.Accept(visitor);
我认为这对于在不同派生的 classes 上简单分派一些操作来说有点过分了。
此外,链从调用 Accept
而不是 Visit
开始是违反直觉的。
另外,如果你添加一个新的 Derived3
class,你总是必须做两件事:Visitor
中的新重载和 [=15= 的覆盖] 方法中的新派生 class。通常你会忘记第二点,得到一个运行时错误,以及其他烦人的事情。
我正在寻找 C# 中 Visitor
模式的更简单、更简洁、更安全的实现。可能吗?
给定 2 个假设:
- 您可以使用 C# 4.0 或更高版本
BaseClass
是抽象的,BaseClass
没有 "default" 行为(即 Visitor
在每个派生上都有不同的特定行为)
我找到了解决方案:
class DynamicVisitor
{
public void Visit(BaseClass b)
{
Visit((dynamic)b);
}
public void Visit(Derived1 b) { /*do something*/ }
public void Visit(Derived2 b) { /*do something*/ }
}
你这样使用它:
var baseClassList = new List<BaseClass>();
//fill baseClassList somehow
var visitor = new Visitor();
foreach(var visitable in baseClassList)
visitor.Visit(visitable);
这样,您不需要任何 Accept
方法,也不需要它覆盖所访问的 类。
您只需要在 Visitor
上编写重载。
重载 Visit(BaseClass b)
确保任何传递的参数至少派生自 BaseClass
,但 dynamic
转换使 Visitor
在运行时选择特定的重载。
显然,如果一个实例恰好是 BaseClass
类型,那么您将无限递归调用 Visit(Base b)
,那是因为我认为 BaseClass
是抽象的:这样您就无法拥有一个 Visit(Base b)
的实例那个类型。
编辑:
我用这个大概的负载做了一些性能测试:
- A
BaseClass
和 17 DerivedClasses
实现了 IVisitable
接口;
- 包含
BaseClass
数百万个实例的列表可供访问;
- a
ClassicVisitor
和 DynamicVisitor
都实现了 IVisitor
接口;
- 所有
Accept
方法只调用Visit
方法,所有Visit
方法都是空的,以最大化测量的时间差异。
- 在调试和发布模式下使用
Stopwatch
测量的时间。
结果:DynamicVisitor
慢了 10 倍以上(~5/600 毫秒对~50 毫秒)。
因此,此解决方案适用于您需要对访问者进行少量调用的情况。
假设您有一个抽象 BaseClass
和一些派生的 classes,并且您需要访问带有 Visitor
的 List<BaseClass>
。代码将是:
class Visitor
{
public void Visit(Derived1 visitable) { /*do something*/ }
public void Visit(Derived2 visitable) { /*do something*/ }
public void Visit(Base visitable) { thrown new InvalidOperationException(); }
//other derivatives
}
class Base
{
public virtual void Accept(Visitor visitor) { visitor.Visit(this); }
}
class Derived1 : Base
{
//override is mandatory to have Visit(Derived1 visitable) called
public override void Accept(Visitor visitor) { visitor.Visit(this); }
}
class Derived2 : Base
{
//override is mandatory to have Visit(Derived2 visitable) called
public override void Accept(Visitor visitor) { visitor.Visit(this); }
}
然后你就可以在如下方法中使用所有这些东西:
var baseClassList = new List<BaseClass>();
//fill baseClassList somehow
var visitor = new Visitor();
foreach(var visitable in baseClassList)
visitable.Accept(visitor);
我认为这对于在不同派生的 classes 上简单分派一些操作来说有点过分了。
此外,链从调用 Accept
而不是 Visit
开始是违反直觉的。
另外,如果你添加一个新的 Derived3
class,你总是必须做两件事:Visitor
中的新重载和 [=15= 的覆盖] 方法中的新派生 class。通常你会忘记第二点,得到一个运行时错误,以及其他烦人的事情。
我正在寻找 C# 中 Visitor
模式的更简单、更简洁、更安全的实现。可能吗?
给定 2 个假设:
- 您可以使用 C# 4.0 或更高版本
BaseClass
是抽象的,BaseClass
没有 "default" 行为(即Visitor
在每个派生上都有不同的特定行为)
我找到了解决方案:
class DynamicVisitor
{
public void Visit(BaseClass b)
{
Visit((dynamic)b);
}
public void Visit(Derived1 b) { /*do something*/ }
public void Visit(Derived2 b) { /*do something*/ }
}
你这样使用它:
var baseClassList = new List<BaseClass>();
//fill baseClassList somehow
var visitor = new Visitor();
foreach(var visitable in baseClassList)
visitor.Visit(visitable);
这样,您不需要任何 Accept
方法,也不需要它覆盖所访问的 类。
您只需要在 Visitor
上编写重载。
重载 Visit(BaseClass b)
确保任何传递的参数至少派生自 BaseClass
,但 dynamic
转换使 Visitor
在运行时选择特定的重载。
显然,如果一个实例恰好是 BaseClass
类型,那么您将无限递归调用 Visit(Base b)
,那是因为我认为 BaseClass
是抽象的:这样您就无法拥有一个 Visit(Base b)
的实例那个类型。
编辑:
我用这个大概的负载做了一些性能测试:
- A
BaseClass
和 17DerivedClasses
实现了IVisitable
接口; - 包含
BaseClass
数百万个实例的列表可供访问; - a
ClassicVisitor
和DynamicVisitor
都实现了IVisitor
接口; - 所有
Accept
方法只调用Visit
方法,所有Visit
方法都是空的,以最大化测量的时间差异。 - 在调试和发布模式下使用
Stopwatch
测量的时间。
结果:DynamicVisitor
慢了 10 倍以上(~5/600 毫秒对~50 毫秒)。
因此,此解决方案适用于您需要对访问者进行少量调用的情况。