經過閱讀各位前輩寫的博文,像呂震宇,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