學習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

相關文章
相關標籤/搜索