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