是否可以在C#中创建用户可扩展的访问者模式? (最好是.net 3.5)
我在库中有一组类,我希望通过访问者模式添加功能。问题是库的用户也可以创建自己的类。这意味着您需要创建一个接受新类类型的特殊访问者,但我们的Accept方法设置为接收基类型。如何让派生类在派生的访问者中调用正确的方法。
还是有另一种方法可以做'如果这种类型,做这个吗?
一些示例代码:
/* In library */
namespace VisitorPattern.System
{
interface IThing
{
void Accept(SystemVisitor visitor);
void ThingMethodA(...);
void ThingMethodB(...);
}
class SystemThingA : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemThingB : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemThingC : IThing
{
public void Accept(SystemVisitor visitor) { visitor.Visit(this); }
...ThingMethods...
}
class SystemVisitor
{
public SystemVisitor(object specialSystemServices) { }
public virtual void Visit(SystemThingA thing) { Console.WriteLine("SystemThingA"); }
public virtual void Visit(SystemThingB thing) { Console.WriteLine("SystemThingB"); }
public virtual void Visit(SystemThingC thing) { Console.WriteLine("SystemThingC"); }
public virtual void Visit(IThing thing) { Console.WriteLine("sysvis:IThing"); }
}
}
/* in user code */
namespace VisitorPattern.User
{
using VisitorPattern.System;
class UserThingA : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
class UserThingB : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
class UserThingC : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
...ThingMethods...
}
// ?????
class UserVisitor : SystemVisitor
{
public UserVisitor(object specialSystemServices, object specialUserServices) : base(specialSystemServices) { }
public void Visit(UserThingA thing) { Console.WriteLine("UserThingA"); }
public void Visit(UserThingB thing) { Console.WriteLine("UserThingB"); }
public void Visit(UserThingC thing) { Console.WriteLine("UserThingC"); }
public override void Visit(IThing thing) { Console.WriteLine("uservis:IThing"); }
}
class Program
{
static void Main(string[] args)
{
var visitor = new UserVisitor("systemservice", "userservice");
List<IThing> mylist = new List<IThing> { new UserThingA(), new SystemThingB(), new SystemThingC(), new UserThingC() };
foreach (var thing in mylist)
{
thing.Accept(visitor);
}
}
}
}
答案 0 :(得分:3)
好像你已经倒退了。首先,我们来谈谈Liskov替代原则。它说任何类型都应该可以被基类型替换。这也适用于访客模式。
如果您有一个名为void Accept(IVisitor visitor)
的方法,那么访问的FancyVisitor
或SipleVisitor
无关紧要。
访问者模式的整个想法是主题(即被访问的类)不应该知道有关访问者的任何内容,而不是它实现的契约(基类或接口) 。并且每个Visitor
类应该特定于正在访问的某个类。
这就是你的代码的问题。您正在尝试创建一个可访问所有系统组件的常规Visitor类。这是完全错误的。
在我看来,你有两个选择:
您希望从所有系统组件中收集相同类型的信息。
易。创建所有系统组件实现的新接口。然后将访问者更改为Visit(ISystemCompoent subject)
。
您希望从每个系统组件收集不同类型的信息
然后,您需要创建不同的访客基类(或接口)。
答案 1 :(得分:2)
不,不可能将访问者模式与可扩展类层次结构的视觉混合在一起。它们是相互排斥的。
答案 2 :(得分:1)
此series of blog posts的一个解决方案可能涉及使用“interfaces and dynamic type casts to overcome the Visitor pattern’s problems with extensible class hierarchies”
例如:
class UserThingC : IThing
{
public void Accept(SystemVisitor visitor)
{
var userVisitor = visitor as UserVisitor;
if (userVisitor == null) throw new ArgumentException("visitor");
userVisitor.Visit(this);
}
}
(不是说这是最好的,只是另一种选择)
答案 3 :(得分:1)
是的,你可以使用反射来做到这一点。使用访问者模式的主要思想是双重调度。使用反射,您可以从任何访问者获取所有Visit(...)
方法,并根据Visit方法的参数类型调用正确的方法。
如果你走这条路,你不一定需要访问者或你正在访问的元素的继承层次结构。实际上,元素类甚至不需要知道访问者接口(或基类)。
为清楚起见,下面是一个代码示例,它实现了一个使用反射进行双重调度的通用访问者。使用GenericVisitor<T>::AcceptVisitor(...)
,只要访问者T
定义了方法Visit(...)
,您就可以获取任何元素(派生与否)在任何访问者(派生与否)中调用正确的方法对于那个特定的元素类。
using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
namespace VisitorPattern
{
class GenericVisitor<T>
{
// Dictionary whose key is the parameter type and value is the MethodInfo for method "Visit(ParameterType)"
static Dictionary<Type, MethodInfo> s_visitorMethodDict;
static GenericVisitor()
{
s_visitorMethodDict = new Dictionary<Type, MethodInfo>();
Type visitorType = typeof(T);
MethodInfo[] visitorMethods = visitorType.GetMethods();
// Loop through all the methods in class T with the name "Visit".
foreach (MethodInfo mi in visitorMethods)
{
if (mi.Name != "Visit")
continue;
// Ignore methods with parameters > 1.
ParameterInfo[] parameters = mi.GetParameters();
if (parameters.Length != 1)
continue;
// Store the method in the dictionary with the parameter type as the key.
ParameterInfo pi = parameters[0];
if (!s_visitorMethodDict.ContainsKey(pi.ParameterType))
s_visitorMethodDict.Add(pi.ParameterType, mi);
}
}
public static bool AcceptVisitor(object element, T visitor)
{
if (element == null || visitor == null)
return false;
Type elementType = element.GetType();
if (!s_visitorMethodDict.ContainsKey(elementType))
return false;
// Get the "Visit" method on the visitor that takes parameter of the elementType
MethodInfo mi = s_visitorMethodDict[elementType];
// Dispatch!
mi.Invoke(visitor, new object[] { element });
return true;
}
}
// Element classes (note: they don't necessarily have to be derived from a base class.)
class A { }
class B { }
class Visitor
{
public void Visit(A a) { System.Console.WriteLine("Visitor: Visited A"); }
public void Visit(B b) { System.Console.WriteLine("Visitor: Visited B"); }
}
interface IVisitor
{
void Visit(A a);
void Visit(B b);
}
class DerivedVisitor : IVisitor
{
public void Visit(A a) { System.Console.WriteLine("DerivedVisitor: Visited A"); }
public void Visit(B b) { System.Console.WriteLine("DerivedVisitor: Visited B"); }
}
class Program
{
static void Main(string[] args)
{
Object a = new A();
Object b = new B();
// Example of Visitor that doesn't use inheritance.
Visitor v1 = new Visitor();
GenericVisitor<Visitor>.AcceptVisitor(a, v1);
GenericVisitor<Visitor>.AcceptVisitor(b, v1);
// Example of Visitor that uses inheritance.
IVisitor v2 = new DerivedVisitor();
GenericVisitor<IVisitor>.AcceptVisitor(a, v2);
GenericVisitor<IVisitor>.AcceptVisitor(b, v2);
}
}
}
答案 4 :(得分:0)
您可以使用新的 动态 关键字:
public class Visitable1
{
public void Accept(dynamic visitor)
{
visitor.Visit(this);
}
}
public class DynamicVisitor
{
public void Visit(Visitable1 token)
{
// Call token methods/properties
}
}
但是,您将代码暴露给 MissingMethodException
答案 5 :(得分:0)
Yes, you can do this.
Change all your UserThing's Accept(SystemVisitor visitor)
methods to accept a UserVisitor
instead.
Add an abstract base class for all your UserThing
s
In the abstract base class add an Accept
method that attempts to cast the visitor from a SystemVisitor
to a UserVisitor
. if it succeeds it calls the Accept method on the UserThing
.
public override void Accept(SystemVisitor visitor)
{
var visitorAsUser = visitor as UserVisitor;
if (visitorAsUser != null)
return this.Accept(UserVisitor);
}
The SystemVisitor
still knows nothing about your UserThing
s and an existing SystemVisitor
cannot visit them, but your UserVisitor
can.