最近更新: 2007-03-16

類別繼承、介面宣告與模組混成(mix-in)

在思考 Ruby 模組與混成(mix-in)概念的過程中,勾起了我當初學習 Java 的記憶。C++ 藉由多重繼承達成程式碼再用之目的,也因此衍生了類別鑽石繼承問題。而 Java 出現時,強調它使用單一繼承並結合介面宣告而避免鑽石繼承問題。然而我對介面的使用經驗卻是負面的。

介面只宣告行為的外觀而不牽涉細節,細節在類之中個別實現。舉例而言,如果有兩個不具共同父祖類別的類,假設為 A, B 類,但具有一個共同的行為、一段相同的程式。 C++ 的作法是將此共同行為 - 亦即這一段相同的程式 - 設計為一個類,假設為 C 類,再令 A, B 類多重繼承 C 類;只要 C 類之中沒有任何屬性與 A, B 類之父祖相同,就不會導致鑽石繼承,同時達成程式碼再用之目的。Java 的作法則有兩個方式,其一是介面,其二是深度繼承。

A,B之中個別保有c的相同複本

介面的設計方式是以介面宣告 A,B 類具有共同行為 c ,接著在 A, B 類之中分別實現 c 的細節。具體而言,先在 A 類寫出 c 的程式碼,然後再把這一段程式碼複製到 B 類。「複製」就是介面宣告的最大缺點。介面只宣告外觀而無細節,所以它不能再用程式碼。如果我們有許多類別皆具有此共同行為,我們就要將同一段程式碼複製許多份。熟悉 XP/Agile methods 的程序員此時一定會大叫 DRY! (Don't Repeat Yourself)。程式碼的剪貼複製絕對是滋養 bug 的溫床。假設我們日後發現這段程式碼有一個 bug ,我們當然要修正。但若它已經為了實現介面內容而被剪貼複製到許多類別之中,修正它就是個大麻煩。誰知道它被複製到多少類別中了?

A,B繼承多個類外才有共同父祖C

深度繼承的設計方式就是設法拉關係。一樣先將共同行為設計為一個類, C 類。接著從 A,B 的父祖輩中拉關係,將 C 類加入 A, B 的繼承樹中,亦即令 C 成為 A, B 的父祖之一。在單一繼承機制下拉關係動作非常麻煩,可能要翻出祖譜來上找十八代才找得到關係。如果我們要查看 A,B 類之共同行為 c 的實作內容,我們可能要上溯十八個父祖類才能在第十八代祖宗的 C 類之中找到行為 c 的內容。

我描述一個極端情形,我把每一個行為都單獨設計成一個類,那麼一個具有18種行為的類,就必須繼承十八代祖宗才能實現。說到這我想到結構化設計中的功能樹了,在深度繼承下的繼承樹,根本就是一棵倒過來的功能樹。結構化設計的功能樹是將功能不斷往下細分到最小功能,因此一個完整的功能就是由底下所有小功能組合而成。把這棵樹倒過來,就成了深度繼承樹:功能最豐富的子類,是自其上所有父祖類別繼承而得。那麼,以往結構化設計時碰得到的問題,在此一樣會出現。

將方法 c 混入A,B中

當年在學 Java 時,我便感覺到介面宣告規避了鑽石繼承問題,卻無助於提高程式碼再用性。 Java 的介面宣告,只是規避而非解決鑽石繼承問題。實務上,鑽石繼承問題也不是那麼容易就碰得到, C++ 的多重繼承一直都好好地運作著。在動態語言中,雖然多數都採用單一繼承機制,卻又毫不死板,憑藉著動態性提供繼承機制以外的程式碼再用方式。 Ruby 的混成(mix-in)機制就是一個聰明的例子。這個概念也開始被程序員應用於其他語言之中,例如我嘗試於 JavaScript 和 PHP 中實踐混成概念 (PHP 實踐 mix-in 概念之可行性)。隨著混成概念於其他語言之實踐方式逐漸完善,對於「繼承」的想法與實踐途徑必然會更加豐富,設計方式也將更具彈性。

相關文章
樂多舊網址: http://blog.roodo.com/rocksaying/archives/2860767.html

樂多舊回應
未留名 (#comment-4188757)
Fri, 16 Mar 2007 12:20:09 +0800
石頭成老大,我記得還有 delegate 可用~~

當然這得看設計的方式,我個人對物件導向設計還沒有什麼豐富的經驗,只是覺得談到介面時, delegate 這個技巧也是要考慮進去的項目。

聽您說要寫繼承的 Part 2 也引發了我一些想法;所以我自己就寫了一篇 我也來實作 PHP mix-in 的概念 - Part 2 ,實作相關的概念。

至於怎麼讓它們能夠繼承,恕我愚眛,到現在還想不出什麼好方法。也很擔心這樣繼承的結果,反而可能會讓後面接手的人看不懂我在幹什麼。

個人粗淺意見,參考看看就好 :)
未留名 (#comment-4189667)
Fri, 16 Mar 2007 14:50:35 +0800
我當年學的是 Java 1.0 ,那時還時沒有 delegate 這東西 (如果我沒記錯的話)。

後來我在 C# 中用過 delegate ,但是很麻煩... 非常麻煩。不會比 PHP 好到哪去。