通过阅读各位前辈写的博文,像吕震宇,idior,李建忠WebCast等,对Visitor模式有一定的了解,有感而记录下来,以备忘。
Visitor Pattern 假设了这样一个场景,在一个类型层次中,如果类型的个数稳定,且对类型操作不稳定(根据需求可能会变化)。在该模式中有个Double Dispatch的概念,即Element抽象一次,Visitor抽象一次多态。还有一次编译时多态(overload)。在Element中有Accept方法,留出以后可能扩展的操作,在ConcreteElement中,有如下关键点
public override void Accept(Visitor v) { v.Visit(this); }
将具体的Element传递到Visitor中,并通过overload确定调用Visit的那个版本重载。该模式将元素的数据结构和对其的操作分离,以后需要添加额外操作添加新的Visitor实现即可。缺点就是类型的个数不变,如果需要添加新类型元素,那么Visitor抽象也需要修改。所以一般抽象的是稳定的,封装的是变化点。
2、方法的重载中,参数的类型是否可以在run-time时,实现绑定呢?在idior的文章中有详细解释,在该文中,去掉Element抽象中的Accept方法,由Visitor中的一个方法Visit(Element e)作为入口,然后动态调用具体的目标重载方法。文中解释过,GOF设计模式,是十几年前的作品,那个时候没有元数据和Reflection,overload是发生在编译时,所以Visitor模式需要double-dispatch。并给出了一个使用反射的方法,如下:
public int Visit(Expression e) { Type[] types = new Type[] { e.GetType() }; MethodInfo mi = this.GetType().GetMethod("Visit", types); if (mi==null) throw new Exception("UnSupported!"); else return (int)mi.Invoke(this,new object[]{e}); }
该方法作为入口,动态调用具体的重载方法。这里对我很有启发,reflection如果在循环中可能会对性能有影响,故考虑缓存优化一下,如下:
class EvaluateVisitor { Dictionary<Type, Func<EvaluateVisitor, Expression, int>> cache = new Dictionary<Type, Func<EvaluateVisitor, Expression, int>>(); /// <summary> /// 根据实际的Type,动态生成一个(对目标方法Visit(XXXExpression e)的直接调用)委托 /// </summary> /// <param name="type"></param> /// <returns></returns> private Func<EvaluateVisitor, Expression, int> BuildFunc(Type type) {//(inst,e)=>inst.Visit((XXXExpression)e); MethodInfo mi = this.GetType().GetMethod("Visit", new Type[] { type }); if (mi == null) throw new Exception("UnSupported!"); LE.ParameterExpression paramExp = LE.Expression.Parameter(typeof(Expression), "e"); LE.ParameterExpression instance = LE.Expression.Parameter(this.GetType(), "inst"); LE.MethodCallExpression methodCallExp = LE.Expression.Call(instance, mi, LE.Expression.Convert(paramExp, type)); var lambda = LE.Expression.Lambda<Func<EvaluateVisitor, Expression, int>>(methodCallExp, instance, paramExp); return lambda.Compile(); } private Func<EvaluateVisitor, Expression, int> GetTargetVisit(Type type) { Func<EvaluateVisitor, Expression, int> result; if (!cache.TryGetValue(type, out result)) { result = BuildFunc(type); cache.Add(type,result); } return result; } public int Visit(ConstantExpression e) { return e.Constant; } public int Visit(SumExpression e) { return Visit(e.Left) + Visit(e.Right); } public int Visit(Expression e) { //Type[] types = new Type[] { e.GetType() }; //MethodInfo mi = this.GetType().GetMethod("Visit", types); //if (mi == null) // throw new Exception("UnSupported!"); //else // return (int)mi.Invoke(this, new object[] { e }); Type t = e.GetType(); var target = GetTargetVisit(t);//在run-time,获取对目标方法的调用 return target(this, e); } }
在这里,对于每一个类型对应重载方法,做一个cache,根据type动态生成一个委托,该委托去调用目标方法(Visit)。这样不用每次都去反射了,提高性能。从这里看出NET3.0之后Expression Tree功能很强大,它允许我们在run-time时候动态生成一个委托,而调用委托的性能和直接调用Method几乎一样。有兴趣的同学可以参考我之前的文章《让CLR帮我写代码》。
3、在.NET中,ExpressionVisitor类用来操作Expression Tree的,也是一个visitor模式的应用,大家有兴趣可以去看看。
先写到这里了,欢迎大家交流,不正之处,还请指正,谢谢!