多態
多態是什麼?一句話:接口和實現的1:n映射。多態讓程序能通過統一的接口(廣義的接口,意指規范 )調用不同的實現,從而增強程序的表達能力和靈活性。我們最為熟悉的多態形式是包括接口繼承在內的 類型多態:
var animals = new List<IAnimal>() {
new Cat("Missy"),
new Cat("Mr. Bojangles"),
new Dog("Lassie")
};
foreach (var animal in animals) {
Console.WriteLine(animal.Name + ": " + animal.Talk());
}
有時,我們常常混淆繼承與多態之間的關系,甚至認為二者是同一概念的不同方面。這裡我嘗試理清 二者之間的區別與聯系。在C#中,繼承有兩方面的功能:1.派生類復用基類的非私有成員和方法;2.實現 類型多態。功能1的本質是“復用”,即派生類通過繼承復用基類代碼,基類與派生類之間處於同一抽象 層次;功能2的本質是“被復用”,即派生類通過繼承以基類/接口的名義被復用,基類與派生類是抽象與 具體的關系。
我們常常把“基類”(base class)隨意稱作“父類”,不加區別,這在概念上是值得商榷的。“父 子關系”是什麼?兒子繼承父親的某些特征,強調的是復用,兒子與父親是同一抽象層次的事物,只體現 了繼承的復用功能。“動物和狗的關系”顯然不同於“父子關系”,動物與狗是抽象與具體的關系,程序 中建立這種關系的主要目的在於以抽象的名義被復用,而非復用基類功能。因此,當繼承的主要目的是復 用時,我們可以把同一抽象層次上的基類稱作“父類”,而當繼承的主要目的是實現類型多態被復用時, “父類”的提法是欠妥的。
正是由於對繼承功能兩個方面欠缺清晰的認識,導致所謂的“濫用繼承”。前文已經談到過,繼承是 很強的類型約束,當目的只是為了復用時,如果采用繼承的方式,往往會帶來一些副作用,比如:在C#中 ,對象在其生命周期內無法改變繼承關系,兒子像老子就必須像一輩子。其實復用並非OO的專利,在幾乎 所有場合,我們都可以通過其他方式實現復用,比如組合方式。因此,這裡給出一條避免濫用繼承的明確 建議:當目的是復用,請考慮采用組合,而非繼承!
泛型委托
多態與繼承沒有必然的聯系,繼承只是實現類型多態的一種手段。我理解,凡是符合接口與實現的1:n 映射都屬於多態。那麼C#中的委托是不是多態呢?當然是,我姑且稱它為方法多態,委托的調用者通過統 一的委托調用不同的方法實現。與繼承多態相比,委托消除了類型約束,凡是符合簽名的方法都可以被委 托調用,因此更加靈活。還有,泛型是不是多態呢?也是。泛型類/方法的編寫者,通過使用統一的類型 參數T表達程序的行為,把T具體化的工作交給泛型類/方法的用戶。
委托是多態,泛型是多態,那麼泛型委托是不是多態呢?當然更是,而其是非常強大的多態!其強大 就在於融合了泛型和委托的抽象能力,又不失靜態類型的安全性。GoF早就曾意識到泛型與委托相結合將 是多麼強大的抽象能力啊!而有幸的是,C#早已在語言層面直接支持泛型委托(據我所知,C++ boost庫 也提供了類似泛型委托的泛型函數指針)。