学习Visitor Pattern 有感而发!override and overload

  经过阅读各位前辈写的博文,像吕震宇idior,李建忠WebCast等,对Visitor模式有必定的了解,有感而记录下来,以备忘。html

  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抽象也须要修改。因此通常抽象的是稳定的,封装的是变化点。缓存

 

二、方法的重载中,参数的类型是否能够在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若是在循环中可能会对性能有影响,故考虑缓存优化一下,以下:ide

    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帮我写代码》。性能

 

三、在.NET中,ExpressionVisitor类用来操做Expression Tree的,也是一个visitor模式的应用,你们有兴趣能够去看看。优化

 

先写到这里了,欢迎你们交流,不正之处,还请指正,谢谢!ui

相关文章
相关标签/搜索