繼承是代碼復(fù)用的最佳方案嗎?
繼承,一個(gè)父類可有許多個(gè)子類。父類就是把一些公共代碼放進(jìn)去,之后在實(shí)現(xiàn)其他子類時(shí),少寫一些代碼。
代碼復(fù)用,很多人覺得繼承就是絕佳方案。若把繼承理解成代碼復(fù)用,更多是站在子類角度向上看。在客戶端代碼使用時(shí),面對(duì)的是子類,這種繼承叫實(shí)現(xiàn)繼承:
還有一種看待繼承的角度:從父類往下看,客戶端使用時(shí),面對(duì)的是父類,這種繼承叫接口繼承:
但接口繼承更多和多態(tài)相關(guān)。本文主要討論實(shí)現(xiàn)繼承。
不推薦實(shí)現(xiàn)繼承:
? 繼承很寶貴,Java只支持單繼承 一個(gè)類只能有一個(gè)父類,一旦繼承的位置被實(shí)現(xiàn)繼承占據(jù),再想做接口繼承就難了
? 實(shí)現(xiàn)繼承通常也是一種受程序設(shè)計(jì)語(yǔ)言局限的思維方式 很多語(yǔ)言,不使用繼承,也有代碼復(fù)用方案
1、案例
產(chǎn)品報(bào)表服務(wù),其中的某服務(wù):查詢產(chǎn)品信息。該查詢過(guò)程通用,別的服務(wù)也可用。所以,我把它放父類以復(fù)用:
ReportService沒(méi)有繼承任何類,但也可復(fù)用代碼,即ProductFetcher模塊。這樣,若我需要有個(gè)獲取產(chǎn)品信息的地方,它不必非得是個(gè)服務(wù),我無(wú)需繼承任何類。
獲取產(chǎn)品信息、生成報(bào)表是兩件事,只是因?yàn)樵谏蓤?bào)表過(guò)程,需要獲取產(chǎn)品信息,所以,它有個(gè)基類。
不用繼承的實(shí)現(xiàn):
這就是組合:ReportService里組合一個(gè)ProductFetcher。設(shè)計(jì)通用原則:組合優(yōu)于繼承。即若一個(gè)方案既能用組合實(shí)現(xiàn),也能用繼承實(shí)現(xiàn),那就用組合。
所以,要寫繼承以實(shí)現(xiàn)代碼復(fù)用時(shí),問(wèn)問(wèn)自己,這是接口繼承,還是實(shí)現(xiàn)繼承?若是實(shí)現(xiàn)繼承,是不是可以寫成組合?
2、面向組合編程
可以組合的根因:獲取產(chǎn)品信息、生成報(bào)表服務(wù)本是兩件事(分離關(guān)注點(diǎn))。你要是看出是兩件事了,就不會(huì)把它們放一起。
分解是設(shè)計(jì)的第一步,分解粒度越小越好。當(dāng)可分解出多個(gè)關(guān)注點(diǎn),每個(gè)關(guān)注點(diǎn)就是個(gè)獨(dú)立類。最終類由這一個(gè)個(gè)小類組合而得,即面向組合編程。按面向組合思維:為增加復(fù)雜度,增加一個(gè)報(bào)表生成器(ReportGenerator),在獲取產(chǎn)品信息后,生成報(bào)表:
OOP面向的是“對(duì)象”,不是類!很多程序員習(xí)慣把對(duì)象理解成類的附屬品,但在Alan Kay的理解中,對(duì)象本身就是獨(dú)立個(gè)體。所以,有些語(yǔ)言支持直接在對(duì)象操作。
現(xiàn)在,想給報(bào)表服務(wù)新增接口:處理產(chǎn)品信息。這樣的處理只會(huì)影響這里的一個(gè)對(duì)象,而同樣是這個(gè)ReportService的其他實(shí)例,則完全不受影響。
- ? 好處 不必寫那么多類,根據(jù)需要,在程序運(yùn)行時(shí)組合出不同對(duì)象。
Java只有類這種組織方式,所以,很多有差異的概念只能用類這一個(gè)概念表示,思維受到限制,不同語(yǔ)言則提供不同的表現(xiàn)形式,讓概念更加清晰。
前面只是面向組合編程在思考方式的轉(zhuǎn)變,現(xiàn)在看設(shè)計(jì)差異。
3 案例
字體類(Font)需求:支持加粗、下劃線、斜體(Italic),且能任意組合。
3.1 繼承
需8個(gè)類:
3.2 組合
字體類(Font)只需三個(gè)獨(dú)立維度:是否加粗、下劃線、斜體。若再來(lái)一種需求,變成4種,采用繼承,類數(shù)量膨脹到16個(gè),而組合只需再增加一個(gè)維度。把一個(gè)M*N問(wèn)題,設(shè)計(jì)轉(zhuǎn)成M+N。
Java在面向組合編程方面能力較弱,但Java在嘗試不同方案。早期嘗試有Qi4j,后來(lái)Java 8加入default method,在一定程度上也可支持面向組合編程。
4、DCI
繼承是OOP原則之一,但編碼實(shí)踐中能用組合盡量使用組合。DCI也是一種編碼規(guī)范,對(duì)OOP的一種補(bǔ)充,核心思想也是關(guān)注點(diǎn)分離。
DCI是對(duì)象的Data數(shù)據(jù), 對(duì)象使用的Context場(chǎng)景, 對(duì)象的Interaction交互行為三者簡(jiǎn)稱, 是一種特別關(guān)注行為的模式(可對(duì)應(yīng)GoF行為模式),而MVC模式是一種結(jié)構(gòu)性模式,DCI可使用演員場(chǎng)景表演來(lái)解釋,某實(shí)體在某場(chǎng)景中扮演包公,實(shí)施包公升堂行為;典型事例是銀行帳戶轉(zhuǎn)帳,轉(zhuǎn)帳這行為按DDD很難劃分到帳號(hào)對(duì)象,它是跨兩個(gè)帳號(hào)實(shí)例之間的行為,可看成是帳號(hào)這個(gè)實(shí)體(PPT,見四色原型)在轉(zhuǎn)帳這個(gè)場(chǎng)景,實(shí)施了鈔票劃轉(zhuǎn)行為,這種新角度更貼近需求和自然,結(jié)合四色原型 DDD和DCI可以一步到位將需求更快地分解落實(shí)為可運(yùn)行的代碼,是國(guó)際上軟件領(lǐng)域的一場(chǎng)革命。摘自 https://www.jdon.com/dci.html
5、總結(jié)
組合優(yōu)于繼承。 復(fù)用方式背后的編程思想:面向組合編程。它給我們提供了一個(gè)不同的視角,但支撐面向組合編程的是分離關(guān)注點(diǎn)。將不同關(guān)注點(diǎn)分離,每個(gè)關(guān)注點(diǎn)成為一個(gè)模塊,在需要時(shí)組裝。面向組合編程,在設(shè)計(jì)本身上有很多優(yōu)秀地方,可降低程序復(fù)雜度,更是思維轉(zhuǎn)變。
參考
? https://www.infoq.cn/article/2007/11/qi4j-intro
? https://en.wikipedia.org/wiki/Data,_context_and_interaction