1: Override VS. Overload
多態可以說是面向對象世界中一件鋒利的武器, 封裝變化是它的能力的體現。但是你聽說過幾種多態?
Simple Polymorphism :the object whose method is called is decided run-time.
multi- polymorphism :the object which method is called is decided upon the type of the argument
如果你對這兩句描述不是很清楚, 那你知道override和overload嗎?Simple Polymorphism 就意味使用了override, 而multi- polymorphism則意味著使用了overload
前者可能你比較熟悉,后者呢?兩者又有什么不同?什么情況下我們會需要后者呢?你見過它們同時出現嗎?
多態是用于封裝變化的,比如常見的那個Shape Draw的例子。Client不用考慮具體是哪個Shape,通過多態自然能調用到相應的那個Shape的Draw方法(whose method)。但是這時我們只有一個變化的對象――Shape。 如果畫的地方也變呢?比如我可以畫在屏幕上, 也可以畫到打印機上。現在我們有兩個同時會變的因素, 那么Draw方法又通過什么來實現封裝變化呢? Simple Polymorphism 顯然是不夠用了,multi- polymorphism 自然也該出場了。
就從一個游戲開始吧,在這個游戲中有一個怪物開門的場景。怪物有很多種,本游戲的出場人物包括了矮人和泰坦,門也有兩種 :一種普通的木頭門, 還有就是很重的鐵門。
現在怪物和門登場了…
interface Monster {}
class Drawf : Monster {}
class Giant : Monster {}
class Door
{
public virtual void OpenBy(Monster monster)
{
Console.WriteLine("Who are u?");
}
{
Console.WriteLine("It's slowly opened");
}
public virtual void OpenBy(Giant giant)
{
Console.WriteLine("It's just easily broken...Crasp!");
}
}
{
public override void OpenBy(Drawf dwarf)
{
Console.WriteLine("It won't open");
}
public override void OpenBy(Giant giant)
{
Console.WriteLine("It's slowly opened");
}
}
這里為了同時封裝兩種變化(怪物和門),我也同時使用了override 和 overload 。
接著怪物開始開門了…
class Game
{
static void
{
Door ironDoor = new HeavyDoor();
ironDoor.OpenBy(new Drawf());
ironDoor.OpenBy(new Giant());
}
}
答案也很明顯
這個例子同時應用了兩種多態, 但是卻只體現了第一種多態封裝變化的效果!如何將兩者都體現出來?看下面的測試
class Game
{
static void
{
Door ironDoor = new HeavyDoor();
List<Monster> monsters = new List<Monster>();
monsters.Add(new Drawf());
monsters.Add(new Giant());
ironDoor.OpenBy(m);
}
}
現在你能猜到結果嗎?仔細想想別急著看答案。
很正常?Ok. 你可以直接去看下一節的內容了,如果猜錯了請先復習一下下面的基礎知識吧.
你肯定聽說過所謂的”動態綁定”,通常意義上的多態也就是通過它實現的,簡而言之――the object whose method is called is decided run-time . 重點就在于這個run-time . 而第二種多態――the object which method is called is decided upon the type of the argument 這里面可沒有出現run-time 倒不是說它不支持,而是不一定,不過在c++, java,c# 中都不支持。 現在你可以理解為什么前面的答案出乎你的意料了。因為后者方法是靜態綁定的, 也就是在編譯期就確定了將要執行哪個Draw方法,而在編譯期,編譯器顯然只能將monsters集合中的對象判斷為Monster類型,從而去執行 void OpenBy(Monster monster)方法。
2: Visitor Pattern without Double Dispatch
通過上一節的例子和解釋, 你應該對override, overload 以及他們各自的綁定機制――動態綁定和靜態綁定有所了解。
接下來看一個更具實際意義的例子。假設我想做一個計算器,那么肯定要先建立一個表達式系統。
abstract class Expression
{
public abstract int Evaluate();
}
{
int constant;
public override int Evaluate()
{
return constant;
}
}
{
Expression left, right;
public override int Evaluate()
{
return left.Evaluate() + right.Evaluate();
}
}
這里利用多態很好的實現了表達式計算的任務。但是把計算的功能放在表達式中并不是一個良好的設計,隨著表達式的類型越來越復雜,可能我們需要對表達式進行語法分析,進行類型檢查, 設置判斷表達式中有多少個常量,多少個變量。如果把這些功能都放在表達式中,一方面不符合責任分離的原則,二來一旦有了新的功能需求我們就要修改所有的表達式對象,維護的惡夢就這樣開始了。
于是我就想到了用Visitor模式將計算的功能從Express類中分離出來。(將過多沒有密切聯系的功能從原來的對象中脫離出來,避免對象過于龐大,這是使用Visitor模式的最重要的原因)。下面是代碼實現,請耐心看完。
abstract class Expression { }
class ConstantExpression : Expression
{
private int constant;
public int Constant
{
get { return constant; }
}
public ConstantExpression(int con)
{
constant = con;
}
}
class SumExpression : Expression
{
private Expression left, right;
public Expression Right
{
get { return right; }
}
public Expression Left
{
get { return left; }
}
{
this.left = left;
this.right = right;
}
}
{
public int Visit(ConstantExpression e)
{
return e.Constant;
}
public int Visit(SumExpression e)
{
return Visit(e.Left) + Visit(e.Right);
}
}
class Program
{
static void
{
ConstantExpression constExp = new ConstantExpression(10);
new ConstantExpression(1));
Console.WriteLine(evalVisitor.Visit(constExp));
Console.WriteLine(evalVisitor.Visit(sumExp));
}
}

看完上面的代碼,可能很多人會有疑問了。 你這里用的是什么Visitor模式?Accept方法呢?IVisitor接口呢? 怎么Visit方法還有返回值?怎么和《Design Pattern》上的Visitor模式完全不是一回事?
為什么要和書上的一樣呢?你難道沒發現以上的疑問都體現了這個版本的優點?沒有Accpet方法意味著在最初的設計中我根本不需要為以后是否需要使用Visitor模式做考慮。沒有IVisitor接口,意味著我可以隨意的定義我的Visit方法,而不需要一個統一的形式,比如這里為了計算方便我讓Visit方法有了返回值。既然這個版本的Visitor模式這么好,怎么Gof沒有想到? 呵呵,露餡了,因為上面的代碼根本無法通過編譯
class EvaluateVisitor
{
public int Visit(ConstantExpression e)
{
return e.Constant;
}
public int Visit(SumExpression e)
{
return Visit(e.Left) + Visit(e.Right);
}
}
聯系第一節介紹的內容,你就會發現盡管e.Left在運行期是ConstantExpression類型,但是由于Overload的方法是靜態綁定的, 而在編譯期e.Left是Express類型, 但是我們根本沒有提供Visit(Expression e)這樣的方法, 編譯自然出錯了。
3: Visitor Pattern with Double Dispatch
回過頭去看看傳統的Visitor模式又是什么樣子的呢?
abstract class Expression
{
public abstract void Accept(Visitor v);
}
class ConstantExpression : Expression
{
private int constant;
public int Constant
{
get { return constant; }
}
{
constant = con;
}
public override void Accept(Visitor v)
{
v.Visit(this);
}
}
class SumExpression : Expression
{
private Expression left, right;
public Expression Right
{
get { return right; }
}
public Expression Left
{
get { return left; }
}
public SumExpression(Expression left, Expression right)
{
this.left = left;
this.right = right;
}
public override void Accept(Visitor v)
{
v.Visit(this);
}
}
interface Visitor
{
void Visit(ConstantExpression e);
void Visit(SumExpression e);
}
class EvaluateVisitor : Visitor
{
private int result;
public int Result { get { return result; } }
{
result=e.Constant;
}
public void Visit(SumExpression e)
{
e.Left.Accept(this);
int lefeRe = this.result;
e.Right.Accept(this);
int rightRe = this.result;
result = lefeRe + rightRe;
}
}
class Program
{
static void
{
ConstantExpression constExp = new ConstantExpression(10);
SumExpression sumExp = new SumExpression(new ConstantExpression(1),
new ConstantExpression(1));
EvaluateVisitor evalVisitor = new EvaluateVisitor();
constExp.Accept(evalVisitor);
Console.WriteLine(evalVisitor.Result);
sumExp.Accept(evalVisitor);
Console.WriteLine(evalVisitor.Result);
}
}

可以看出這張圖和前者相比,復雜了許多。但是只有這樣我們才能在C#這種不直接支持Double Dispatch的語言中實現Visitor模式。
怎么解決的呢? 道理很簡單就是用Twice Single Dispatch來模擬Double Dispatch。最能體現Twice Single Dispatch的代碼如下:
public override void Accept(Visitor v)
{
v.Visit(this);
}
其一般形式為:
public void MethodA(A a)
{
a.MethodB(this);
}
其實說到底,不支持Double Dispatch就是由于不支持方法參數的動態綁定, 那我們就通過兩次的O虛方法的動態綁定來模擬它。所以 a 又從方法參數變成了虛方法的擁有者,并把this作為參數傳入。
明白了以上的道理,你也就知道為什么完全同樣的Accpet方法沒有被提到它們的基類中――-因為方法參數的類型是在編譯期決定的。
看到了傳統Visitor模式的解決思路,對于文章一開始的小游戲所引起的問題也就不難解決了。
4: Visitor Pattern with Reflection

再來看看第二節的模型,實在是很漂亮,讓我不忍放棄。Gof 也想不出什么好的辦法,只能無奈的采用了第三節的復雜模型。 不過,你記得嗎?《Design Pattern》那本書可是十年前的作品。十年前可沒有什么元數據,自然也沒有Reflection。
通過反射我完全可以在運行期從函數參數中找回失去的類型信息。
我只要在第二節的EvaluateVisitor類中添加如下的一個方法,就萬事告吉了。
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});
}
這個方法我個人是比較滿意了,你覺得呢? 在寫sample的時候,我總覺得泛型也應該能解決這個問題,不過在寫完后也沒想到一個比較方便的解決方案。可能是對泛型了解的還不夠,裝配腦袋來試試?
參考資料:
文章開始的Game的各種版本實現
Generic Double Dispatch Engine
Visitor模式全解
浙公網安備 33010602011771號