在本系列的上一篇中,談到了介面和委託文法約束強度的比較,我的結論是介面的文法約束要強於委託。這個話題得到了不少朋友的關注和討論。對此,我在綜合反饋,查閱資料,加上自己的理解的基礎上對介面和委託的關係進行一個小小的總結,並藉此推動本篇的介紹。
一方面,從OO角度看,介面和委託是實現多態性的兩種手段;另一方面,從軟體設計角度看,介面和委託是將規範與實現分開從而面向抽象編程的兩種手段。因此,就存在的意義而言,介面和委託有著重要的聯絡。雖然委託不能覆蓋介面所有的功能,關於文法約束強度的比較沒有全面的說服力,但我們明顯能體會到委託比介面更加靈活。
兩種哲學
這裡尤其值得注意的是介面和委託所代表的兩種哲學:繼承哲學和鴨子哲學。繼承哲學關注對象繼承結構,即“你繼承什麼你就是什麼”;鴨子哲學關注對象的性質,即“你能做什麼你就是什麼”。PS:“鴨子哲學”的名稱來源於Duck Typing的相關論述“如果一隻動物,走起來像鴨子,叫起來像鴨子,那我可以把它當鴨子”。
需要強調的是,繼承哲學不止體現在介面上,鴨子哲學也不只體現在委託上。而兩種哲學,也各有優勢,各有適用場合,沒有高下,這裡只為看清它們的差別,以靈活運用。
讓我們先來看一個例子:對體育進行領域建模,這裡我們只關注教練和隊員的建模。
從繼承到組合
按繼承哲學的方法論”是什麼就應該繼承什麼”,我們很容易想到定義IPlayer,ICoach等介面,讓隊員繼承(實現)IPlayer,讓教練繼承ICoach。一般情況下,這本來沒有什麼問題,但考慮到對象生命週期這個關鍵因素,情況就可能有所不同。比如,我們知道劉國梁原來是乒乓球隊員,後來當上了教練,要表達劉國梁從隊員到教練的轉變就不那麼容易。因為,C#是靜態類型語言,繼承關係是在編譯時間確定的,無法在運行時刪除繼承關係(劉國梁退役),也無法在運行時增加繼承關係(劉國梁當教練)。如果要勉強認為隊員劉國梁和教練劉國梁是兩個對象也是可以的,不過這必然影響領域模型的表達是否自然。
很多有經驗的朋友可能已經意識到:C#中繼承是很強的靜態約束,如果領域模型足夠複雜,在對象生命週期內,其具有的行為可能會發生變化,那麼必須慎用繼承。
用組合代替繼承是增加模型靈活性的常見手段,一般方法是將行為抽象為行為類/介面/委託,比如:定義IPlay,ITeach行為介面,並為對象增加行為屬性Play和Teach。雖然組合關係在C#中也是無法動態增加和刪除的,但可以採取折衷的方式,具體到上面的例子,對象整個生命週期內都必須具有Play和Teach的行為屬性,並且通過拋出運行時異常來表達對象還不具備某種行為。
繼承到組合的轉變在一定程度上顯示了鴨子哲學的靈活性。實際上,所謂“行為”正是“能做什麼”。通過上面的例子不難發現,組合關係通過把焦點從“繼承什麼”轉移到“能做什麼”獲得了更大的靈活性。這個轉變一點兒不勉強,很自然,很符合領域的本質。雖然,C#中組合關係也是靜態,但已經比繼承具有了更多的動態元素,而動態和靜態本身沒有優劣,必鬚根據領域建模需要把握分寸。
後續
下一篇,我將重點介紹鴨子哲學更純正的體現:Duck Typing,敬請關注!
修改曆史:
1. 2009-03-16 初版