繼承的愛恨情仇——一場鉆石引發的血案
最近在看PHP手冊,發現了一個稀奇古怪的新玩意——trait。
這引起了我極大的興趣,由於PHP面向對象的部分有很大程度和Java類似,我就自覺不自覺地和Java對比著來看。
這又讓我想起了那個古老的故事——單繼承和多繼承。
說到這個問題,我最先想起的應該是C++和Java
圖片來源:
編程語言擬人化(1):Java、C++、Python、Ruby、PHP、C#、JS
趣文:編程語言擬人化(第二彈)
這系列文章的原始出處,是日本的「リクナビ NEXT」這家日本的人力公司的《Java、C++、Python…プログラミング言語擬人化計畫! 》和《Perl、C、Scala…プログラミング言語擬人化計畫 2》;他基本上是針對幾種常見的程式語言去設計了萌化的腳色、以及根據該語言發展歷史的人設。知乎用戶:shawn sharp
鏈接:https://www.zhihu.com/question/25038841/answer/44433042
間接來源:知乎
眾所周知,C++是個禦姐,Java是個蘿莉....啊不對....C++支持多繼承而Java不支持。
咋一看,覺得可能是Java做對了,C++的是落後粗野的方法。可是真的是這樣嗎?
難道多繼承真的一無是處,百害而無一利嗎?
顯然不是這樣的,當我們要用多繼承時,我們期待的到底是什麽效果呢?
我們期待的是:可以直接拿來用的類成員函數(方法)。這很重要嗎?對很重要,因為這正是代碼復用所在。
可說到代碼復用首先想到的應該是普通函數,它不行嗎?不行。像是Java這樣的語言他本來就不支持普通函數,再者也是更重要的,我們要類成員函數的意義在於他有面向對象的一系列“紅利”,這才是我們關心的,比如:覆蓋、訪問控制、多態等等,所以這些特性是普通函數所無法替代的也是我們迫切所需的。
當然,得到了這些“紅利”的同時,我們往往也在被“餵屎”,最著名的便是“鉆石繼承”問題。
圖片來自維基百科:Multiple inheritance
鉆石繼承當然有很多弊端,最常見的便是當B和C同時繼承自A,然後D又繼承了B和C。那假設B和C中都有一個成員函數x(),那麽在D中這個x()函數將會用誰的版本呢?B的?還是C的?還有就是,假設A有一個成員變量y,它的構造函數會初始化它,那在D的構造函數執行的時候,它要初始化幾個y呢,在D中的y又是哪一個呢?
這一系列問題看著就讓人頭大...恩,事實是C++居然都能有相應對策(不得不說,禦姐賽高)。可是真的是心累啊...誰想整天處理這些鬼東西!人類之所以強大在於把復雜的問題簡化而解決之,而不是楞頭去啃復雜問題!
所以這個時候,trait就來了!
真的沒有一種既能享受面向對象“紅利”,又不用解決這麽令人掉頭發的復雜問題的東西嗎?!當然有啦!它就是trait。
那trait究竟是個什麽東西呢?拋去具體語法不談,trait就是一個沒有構造函數,不能被實例化的抽象類。哦...那問題不是還沒被解決嗎?!這不還跟原來一個樣嗎!
其實一開始我也很疑惑,這家夥到底和抽象類有啥區別!其實啊,還真沒啥,就一個重大區別,那就是:在遇到沖突時,它能指定到底用哪個版本的成員函數。恩?什麽意思?
就像我上文提到的那個問題,在B和C中分別有一個x(),在D中就發生了沖突,不知道該用哪個版本了。trait能指定,現在,在D中,我們用哪個x(),並且!沒有被指定的那個,就像是暫時被舍棄了,就像不存在一樣。所以,這是關鍵,這就是比C++進步的地方,C++也能指定用那個版本,但是未被指定的那個也還是在的。只有未被指定的被舍棄,才能從根本上解決二義性問題。
這樣我們就能好好地享用“紅利”而不再擔心被“餵屎”了!
語言的發展的過程就是博弈的過程,就是取舍的過程啊!至少在這一點上,看來是這樣的。
最後附上一張PHP的,出處同上
繼承的愛恨情仇——一場鉆石引發的血案