天天看點

《JavaScript應用程式設計》一一3.1 過時的類繼承

本節書摘來華章計算機出版社《javascript應用程式設計》一書中的第3章,第3.1節,作者:eric elliott 更多章節内容可以通路雲栖社群“華章計算機”公衆号檢視。

不知道自己正走在黑暗中的人是永遠不會去搜尋光明的。

——李小龍

在《設計模式:可複用面向對象軟體的基礎》一書的首章,“四人幫”向我們介紹了面向對象設計的兩大基本原則:

面向接口程式設計,而非面向實作程式設計。

優先使用(對象)組合,而非(類)繼承。

從某種角度上說,第二條原則是從第一條中派生而來,因為父類通過“繼承”将其内部結構直接暴露給了所有子類,而子類從本質上來說都是在面向實作程式設計,并非面向接口。類繼承違反了代碼封裝的原則,它使得子類與其祖先之間産生了緊耦合。

我們可以将類繼承比作是宜家的家具,假設你買來一堆家具零部件,它們需要按照一定方式組裝在一起,如果每個零部件都嚴格參照說明書中的描述來組裝,那麼大部分情況下你的家具不會有問題;但如果碰巧某個零部件本身存在缺陷,或者在組裝時沒有按照說明書中的操作來做,則家具可能就此就報廢了。如果把這個例子放在軟體開發領域中會得到這樣一條結論:代碼設計總是在不停地變化。

組合則看起來更像是樂高積木,沒有誰規定說積木非得要搭建成什麼樣子,所有積木之間都可以做自由組合,進而形成不同形狀、不同大小的物體(玩具)。

當你在為類繼承做代碼設計時,你總是在思考如何從具體父類中派生出子類。父類大都會将名稱直接寫死進子類,完全不給你做重寫的機會。使用類繼承等于一開始就将自己囚禁了起來,限制了代碼複用性不說,關鍵會導緻你不會從一個基本層面上反思代碼設計。

而當你在為組合做代碼設計時,你隻需要留心多個對象間的屬性是否會有沖突就可以了,總體上來說整個設計的過程中不會有太多的限制,對象可以根據你的設計做任意組合與重用。一旦你掌握了組合的要領,相比類繼承來說,在代碼設計上将獲得更大的自由度。對于那些常年在類繼承體系下工作的程式員來說,學習與掌握組合(尤其是使用原型技術)就像是在漆黑狹窄的隧道中,發現遠方的一束光亮一樣,迎接他的是一個充滿了各種可能性的嶄新世界。

我們先暫時回到《設計模式》這本書中來,為什麼這本面向對象領域的著作會如此旗幟鮮明地反對繼承?因為繼承會引發下列問題:

對象或子產品間的強耦合:

在面向對象程式設計中,繼承是最容易導緻強耦合的代碼複用方式,因為派生類對其祖先類的一切都十分了解。

繼承層級結構缺乏靈活性:

單一父類的繼承層級結構本身就很難覆寫到所有功能用例,而且對新加入的功能用例的适配性也很差,最終導緻代碼彼此重複。

多重繼承同時帶來了代碼複雜度:

讓子類繼承多個父類,這從代碼複用的角度看來十分具有誘惑性,但它實作的過程不僅非常複雜,而且與單繼承有很大的差異性,這常常也是多重繼承的代碼結構非常晦澀的原因。

架構變得極為不穩定:

一般情況下,由于強耦合的存在,使得你很難對一個采用了“錯誤”設計的類進行重構,因為有太多的現有功能依賴它。

大猩猩與香蕉問題的産生:

在父類中,總會有一些特性是你不想讓子類繼承的,子類可以重寫父類的部分特性,但它決定不了哪些特性是你真正想要從父類中繼承的。

面向對象語言的問題,就在于它本身所持有的隐性語言環境。好比說你想要一根香蕉,它卻把握着香蕉的大猩猩扔給你,而有時甚至會是整個森林。

繼承隻能夠帶來一時的效率提升,而到了後期應用的架構,就像是患了關節炎一樣,變得行動遲緩起來。當你将整個應用搭建在類繼承體系上時,任何對代碼的細微修改都可能導緻大範圍的代碼重構,這是因為上層祖先類間的依賴關系往往是非常緊密的。繼承樹過深,會導緻代碼喪失靈活性、易碎且難于擴充。

一般來說,在一個嚴重依賴于類繼承的應用程式中,會有大量祖先類的存在,這些祖先類互相間略有差異,但是配置卻很相像,這使得分辨它們各自的用途成了一件難事,而且應用很快便被一群看起來極為相似,行為卻十分迥異的執行個體對象所充斥。“重寫”一詞多半就是在這個時候被抛出來的,人們仿佛覺得這樣做,會比代碼重構來的要便捷。

《設計模式》一書中的絕大部分内容其實就是針對上述這些問題的解決方案,但是這些方案有時會顯得較為煩瑣,反而給程式引入了更多的代碼複雜度。有趣的是,這本書的内容讀起來會讓人感覺像是對所有面向對象語言的一次集體揭短。雖然說你可以将書中的所有模式直接搬進javascript中,不過建議你在使用它們之前,最好先把javascript中原型與函數的概念了解清楚。

很多人一直以來都不太明白javascript究竟是不是一門真正的面向對象語言,因為他們認為,javascript相比其他面向對象語言來說,總有些缺胳膊少腿的感覺。在這裡,我們抛開javascript 可以模拟類繼承的能力(與多數基于類的語言相比代碼要更為精簡)不談,如果你剛接觸javascript就開始尋思着如何引入類繼承,就好比是拿着智能觸屏手機問别人撥号轉盤在哪兒一樣,此時如果你一本正經地告訴别人:“它根本就不是電話機,因為它沒有撥号轉盤”,人們會認為你在搞笑。

如繼承、資料隐蔽性、多态等,這些原本出自于其他語言的面向對象特性,在javascript中都可以被模拟,但javascript與生俱來的一些語言特性反而會讓人們覺得,他們可以在javascript中完全廢棄這些基于類繼承的編碼模式。是以别再去糾結諸如“我如何在javascript中實作類繼承”之類的問題了,反而你更應該去思考:“javascript能夠帶給我哪些全新的特性?”。

警告: 我希望你能明白一點,你永遠不需要在javascript中使用類繼承。說是這麼說,但由于類繼承在javascript 中非常容易被模拟,加之有很多人都有傳統面向對象語言的程式設計背景,是以導緻市面上許多類庫都有意引入了類繼承的概念,這其中就包括 backbone.js,我會在後續章節對它進行介紹。如果你在編碼中不得不使用類繼承,那麼請嚴格控制繼承的層級深度。有很多代碼複用方式能夠代替繼承,它們往往具有更好的延展性。

繼續閱讀