C++程式設計批評系列 繼承的本質(轉)

ba發表於2007-08-15
C++程式設計批評系列 繼承的本質(轉)[@more@]Eiffel和C++都提供了多繼承的機制。但Java卻沒有,因為它認為多繼承會導致許多問題的出現。不過Java提供了介面(interface)作為一種替換機制,它類似於Objective C中的協議(protocol)。Sun宣稱介面可以提供多繼承所能提供的所有特性。

Sun所宣稱的“多繼承會帶來許多的問題”這個觀點是對的,尤其是在C++中用以實現多繼承的方法更能說明這一點。那些看起來似乎使用多繼承會比單繼承更簡單的理由,現在都以被證明是毫無意義。例如,如何制訂對於從兩個類之上繼承得到的具有相同名字的資料項之間的策略?它們之間是否相容?如果是的話,那他們是否應該被合併成為一個實體?如果不相容,那應該如何區分它們?……這樣的列表可以列出很長很長。

Java的介面機制也可以用以實現多繼承,但它也有一個很重要的不同之處(與C++ 相比):繼承中的介面必須是抽象的。由於使用介面並沒有任何的實作,這就消除了需要從不同實作之間選擇的可能。Java允許在介面中宣告具有常數字段。當需要多繼承時,他們就合併成為一個實體,這樣也就不會導致歧義的產生。但是,當這些常數具有不同的值時,又有什麼會發生呢?

由於 Java不支援多繼承,我們就不可以像在C++和Eiffel中那樣使用混合(mixin)了。混合是一種特性,它可以把從不同的類中得到的不同的非抽象的函式放到一起形成一個新的複雜的類。例如,我們可能希望從不同的原始碼中匯入一些utility函式。然而,我們也可以透過使用組合而不是繼承來達到同樣的效果,因此,這也就不會對Java構成一個重要的攻擊了。

Eiffel在解決多繼承問題時並沒有匯入一個單獨的介面機制。

有些人可能認為,相對於多繼承來說,單繼承更優雅一些。這是一個很特別的觀點。

BETA [Madsen 93]就屬於認為“多繼承不優雅”的那一種:“Beta中沒有多繼承,這主要是因為(對於多繼承)缺乏一個深刻的理論上的理解,並且當前的(對於多繼承的)建議在技術上看來也非常複雜”。他們引用了Flavors(一種可以將類混合在一起的語言)為證據。與Madsen相比,Flavors中的多繼承與其順序有關,也就是說,繼承自(A,B)和繼承自(B,A)是不一樣的。

Ada95是另一種不支援多繼承的語言。Ada95支援單繼承,並把它叫做標記型別擴充套件(tagged type extension)。

另外一些人認為,對於某些特殊模型下的問題,多繼承可以提供優雅的解法,因此為之付出的努力也是值得的。雖然上面所列出的關於多繼承的問題列表並不完善,它仍然顯示:與多繼承相關的問題是可以被系統地辨識出來的,而一旦問題被確認,它們也就可以被優雅地解決。當[Sakkinen 92]對於多繼承研究到達一個很深的程度後,它就得出了上述定義。

Eiffel中採用的方法是,多繼承會引發一些有趣的且有挑戰性的問題,然後再優雅地解決它們。程式設計師所需做的所有決定都被限制在類的繼承子句中。它包括使用renaming來保證眾多從繼承中得來的同名特性最終成為具有不同名字的特性,對於繼承而來的特性所施展的新的export策略:redefining和undefining,以及用來消除歧義的select。在所有的情況下,編譯器都會為我們做好這一切,為了使得語義清晰而不管是選擇使用fork或是join,程式設計師都具有完全的控制權。

C++ 中相對Eiffel來說有著另外一種不同的用於消除歧義的機制。在Eiffel中,在renames子句中,特性間必須有著不同的名字。在C++中,可以使用域解析運算子’::’來區分成員。Eiffel的做法好處在於,歧義在宣告中就被消除掉了。Eiffel的繼承子句相對C++的來說要複雜不少,但它的程式碼也顯得更簡單,更穩固,並更具彈性。這也就是宣告方法與運算子方法相比的好處所在。在C++中,每次當我們碰到在多個成員間具有歧義時,我們必須在程式碼中使用域解析運算子。這經使得程式碼變得混亂不堪,影響其延展性,如果有其他地方的改變會影響歧義時,我們可能就需要在歧義可能出現的每個地方改變已有的程式碼。

依照[Stroustrup 94]中12.8節所說,ANSI委員會考慮過使用renaming,但是這個提議被委員會中的一個成員所阻塞掉了,他堅持讓委員會中的其他成員用兩週時間來好好地考慮這個問題。在12.8節中給出的例子顯示了在沒有顯示的renaming的前提下,如何做可以得到同樣的效果。問題在於,如果這都需要那些專家們使用兩週來考慮如何實現,那留給我們的空間又有多少呢?

域解析運算子並不只是被用來消除多繼承所帶來的歧義。由於設計良好的語言可以避免歧義的出現,因此域解析運算子也就是一個醜陋的,加深複雜性的實作手法。

在C++中,“如何來宣告多繼承中的父類們”是一個很複雜的問題。它影響到了建構函式被呼叫的次序,當程式設計師確實想從子類轉到父類時也會導致問題的出現。然而,我們也可以把這個稱為不好的程式設計風格。

C++和Eiffel的另一個不同之處在於直接的重複繼承,Eiffel中允許:
class B inherit A, A end 但
class B : public A, public A { };
卻不被C++認可。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10617731/viewspace-957674/,如需轉載,請註明出處,否則將追究法律責任。

相關文章