繼承的特性 ?

侯捷 2000.04.16


這裡有一封 BBS/News OOP 版的貼信,引起我的興趣。原信如下:

發信人: Leaf.bbs@bbs.yzu.edu.tw (自是溫柔最傷人), 看板: oop
標 題: Re: 繼承的特性 ??
發信站: 元智大學風之塔 (Wed Apr 12 22:01:15 2000)

> >有時繼承的使用,並不遵守現實上的觀念
> >例如:
> >圓是一種橢圓
> >所以繼承時,應該要先寫一個橢圓
> >然後寫一個圓去繼承橢圓

> 這種繼承的方式有可能會違反 Liskov-Substitution Principle

> >所以 橢圓 比 圓 多了一個 data member
> >故我們會寫成
> >橢圓繼承了圓

> 這種方式只是為了繼承圓的 implementation 更是錯誤示範.
> 既然橢圓不是圓就不該繼承圓.
> 在這個情形下最好圓跟橢圓不要互相繼承, 但可以讓兩者
> 有一個共同繼承的 base class.

恩...王蟲兄所言甚是

當初上 侯 Sir 開的課時, 就覺得他舉這個例子怪怪ㄉ
但一直找不到什麼說法來解釋
現在總算一語道破了說

也就是說
繼承樹就會變成

[fig]

恩恩恩恩.....
可是這樣會不會增加管理的複雜度啊....


以下是侯捷的感想:

1. 上課教學所舉例子,有時候視當時使用時機(是否為主講重點,或只是為了有個繼承體系以做其他講題用途)、講課對象的程度(是否有一半以上的人從未聽聞所謂 interface inheritance),而有不同深度的討論。如有同學程度超過老師的預期,宜於上課或下課繼續追問疑點,可避免讓自己的疑惑一直成為疑惑。

2. 《多型與虛擬》書中亦曾舉過類似上述「以橢圓繼承圓」的例子,確屬粗糙。然亦不可能於當時剖析 implementation inheritance 和 interface inheritance 的區別。因為當時的情況是希望給一個「儘量簡化」的繼承體系,以做為「多型」之操練對象。

3. 粗糙就是粗糙,所以比較好的作法是在舉「以橢圓繼承圓」的例子時,加註少量補充說明與提醒。我將於《多型與虛擬》2/e 中這麼做。

4. inheritance 是個複雜的題目,涵蓋 public/protected/private
base classes,virtual/nonvirtual base classes;virtual/nonvirtual
member functions。欲釐清所有這些觀念,以網路上的簡短討論做為
學習之道並非智舉。最佳辦法就是找一本好書。

5. 推薦一本好書:Effective C++ 2/e,其中有一章專論「繼承機制與物件導向設計(Inheritance and Object-Oriented Design)。內容包括:

條款35:確定你的 public inheritance 模塑出 "isa" 的關係
Item 35: Make sure public inheritance models "isa."

條款36:區分「介面繼承(interface inheritance)」和
「實作繼承(implementation inheritance)」
Item 36: Differentiate between inheritance of interface and
inheritance of implementation.

條款37:絕對不要重新定義一個繼承而來的非虛擬函式
Item 37: Never redefine an inherited nonvirtual function.

條款38:絕對不要重新定義一個繼承而來的預設參數值
Item 38: Never redefine an inherited default parameter value.

條款39:避免在繼承體系中做 cast down(向下轉型)的動作
Item 39: Avoid casts down the inheritance hierarchy.

條款40:透過 layering(分層技術)來模塑 has-a 或
is-implemented-in-terms-of 的關係
Item 40: Model "has-a" or "is-implemented-in-terms-of"
through layering.

條款41:區分 inheritance 和 templates
Item 41: Differentiate between inheritance and templates.

條款42:明智地運用 private inheritance(私有繼承)
Item 42: Use private inheritance judiciously.

條款43:明智地運用多重繼承(multiple inheritance,MI)
Item 43: Use multiple inheritance judiciously.

條款44:說出你的意思並瞭解你所說的每一句話
Item 44: Say what you mean; understand what you're saying.

6. 在「正方形和矩形」(或是橢圓形和圓形)的繼承關係上,
Effective C++ 2/e item35 有這樣的敘述(摘錄):

p158~p160 :
或許你會說,你對於鳥類實在缺乏直覺,但是你的幾何學學得不錯。喔,是嗎?我想說的是,正方形和矩形之間可能有多麼複雜?

好,請你回答這個問題:class Square 應該以 public 的方式繼承 class Rectangle 嗎?



『咄!』你說,『當然應該如此!每一個人都知道正方形是一種矩形,反之則不一定』。這是真理,至少高中數學課本是這麼教的。但是我不認為你現在還是高中學生。

考慮這份碼:
...
現在再考慮這份碼,其中使用 public inheritance,允許正方形被視為一種矩形:
...

歡迎來到 public inheritance 的奇異世界。你在其他領域(包括數學)學習而得的直覺,在這裡恐怕無法如預期般地有所表現。本例的根本困難是,某些事情可施行於矩形身上(例如寬度可單獨被修改),卻不可施行於正方形身上(其寬度總是應該和高度一樣)。但是 public inheritance主張,每一件事情只要能夠施行於 base class objects 身上 — 每件事情唷! — 就一定可以施行於 derived class objects 身上。在正方形和矩形的例子中(另一個類似的例子是 sets 和 lists,見條款40),那樣的主張無法保持,所以以 public inheritance 來模塑它們之間的關係是錯誤的。編譯器會讓你通過,但是一如我們所見,這並不保證程式的行為適當(或正確)。每個程式員都必須學得一個教訓:程式碼通過編譯,並不表示就可以正確運作。

現在,不要因為你發展經年的軟體直覺在與物件導向觀念打交道的過程中失去效用,便心慌意亂起來。那些知識還是有價值的,但現在你已經為你的軍械庫加上 inheritance(繼承)這支大砲,你也必須為你的直覺添加新的洞察力,以便引導你適當地運用inheritance 這支神兵利器。當有一天有人展示一個數頁長的函式給你看,你終將回憶起「令 Penguin 繼承 Bird,或是令 Square 繼承 Rectangle」的概念和趣味;這種繼承方式有可能接近事實真象,但也有可能不是。

是的,當然,「是一種(isa)」並不是唯一存在於 classes 之間的關係。另兩個常見關係是「有一個(has-a)」和「根據某物實作出(is-implemented-in-terms-of)」。這些關係將在條款40和42討論。將上述這些重要的相互關係中的任何一個誤塑為 isa 而造成的錯誤設計,在 C++ 中實不罕見,所以你應該確定你確實瞭解這些「classes 相互關係」間的差異,並且知道
在 C++ 中如何塑造它們。

-- the end