10丨理論七為何說要多用組合少用繼承如何決定該用組合還是繼承_W_第1頁
10丨理論七為何說要多用組合少用繼承如何決定該用組合還是繼承_W_第2頁
10丨理論七為何說要多用組合少用繼承如何決定該用組合還是繼承_W_第3頁
10丨理論七為何說要多用組合少用繼承如何決定該用組合還是繼承_W_第4頁
10丨理論七為何說要多用組合少用繼承如何決定該用組合還是繼承_W_第5頁
已閱讀5頁,還剩8頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

1、10 | 理論七:為何說要多用組合少用繼承?如何決定該用組合還是繼承?2019-11-25 王爭(zhēng)設(shè)計(jì)模式之美進(jìn)入課程講述:馮永吉時(shí)長(zhǎng) 10:51 大小 9.95M在面向?qū)ο缶幊讨?,有一條非常經(jīng)典的設(shè)計(jì)原則,那就是:組合優(yōu)于繼承,多用組合少用繼承。為什么不推薦使用繼承?組合相比繼承有哪些優(yōu)勢(shì)?如何判斷該用組合還是繼承?今天,我們就圍繞著這三個(gè)問題,來詳細(xì)講解一下這條設(shè)計(jì)原則。話不多說,讓我們正式開始今天的學(xué)習(xí)吧!為什么不推薦使用繼承?繼承是面向?qū)ο蟮乃拇筇匦灾?,用來表示類之間的 is-a 關(guān)系,可以解決代碼復(fù)用的問題。雖然繼承有諸多作用,但繼承層次過深、過復(fù)雜,也會(huì)影響到代碼的可維護(hù)性。所以,

2、下載APP對(duì)于是否應(yīng)該在項(xiàng)目中使用繼承,網(wǎng)上有很多爭(zhēng)議。很多人覺得繼承是一種反模式,應(yīng)該盡量少用,甚至不用。為什么會(huì)有這樣的爭(zhēng)議?我們通過一個(gè)例子來解釋一下。假設(shè)我們要設(shè)計(jì)一個(gè)關(guān)于鳥的類。我們將“鳥類”這樣一個(gè)抽象的事物概念,定義為一個(gè)抽象類 AbstractBird。所有更細(xì)分的鳥,比如麻雀、鴿子、烏鴉等,都繼承這個(gè)抽象類。我們知道,大部分鳥都會(huì)飛,那我們可不可以在 AbstractBird 抽象類中,定義一個(gè) fly() 方法呢?答案是的。盡管大部分鳥都會(huì)飛,但也有特例,比如鴕鳥就不會(huì)飛。鴕鳥繼承具有 fly() 方法的父類,那鴕鳥就具有“飛”這樣的行為,這顯然不符合我們對(duì)現(xiàn)實(shí)世界中事物的

3、認(rèn)識(shí)。當(dāng)然,你可能會(huì)說,我在鴕鳥這個(gè)子類中重寫(override)fly() 方法,讓它拋出 UnSupportedMethodException 異常不就可以了嗎?具體的代碼實(shí)現(xiàn)如下所示:復(fù)制代碼1234567891011public class AbstractBird /. 省略其他屬性和方法.public void fly() /. public class Ostrich extends AbstractBird / 鴕 鳥/. 省略其他屬性和方法.public void fly() throw new UnSupportedMethodException(I cant fly.);

4、這種設(shè)計(jì)思路雖然可以解決問題,但不夠優(yōu)美。因?yàn)槌锁r鳥之外,不會(huì)飛的鳥還有很多, 比如企鵝。對(duì)于這些不會(huì)飛的鳥來說,我們都需要重寫 fly() 方法,拋出異常。這樣的設(shè)計(jì),一方面,徒增了編碼的工作量;另一方面,也違背了我們之后要講的最小知識(shí)原則(Least Knowledge Principle,也叫最少知識(shí)原則或者迪米特法則),暴露不該暴露的接口給外部,增加了類使用過程中被誤用的概率。你可能又會(huì)說,那我們?cè)偻ㄟ^ AbstractBird 類派生出兩個(gè)更加細(xì)分的抽象類:會(huì)飛的鳥類AbstractFlyableBird 和不會(huì)飛的鳥類 AbstractUnFlyableBird,讓麻雀、烏鴉這些

5、會(huì)飛的鳥都繼承 AbstractFlyableBird,讓鴕鳥、企鵝這些不會(huì)飛的鳥,都繼承AbstractUnFlyableBird 類,不就可以了嗎?具體的繼承關(guān)系如下圖所示:從圖中我們可以看出,繼承關(guān)系變成了三層。不過,整體上來講,目前的繼承關(guān)系還比較簡(jiǎn)單,層次比較淺,也算是一種可以接受的設(shè)計(jì)思路。我們?cè)倮^續(xù)加點(diǎn)難度。在剛剛這個(gè)場(chǎng)景 中,我們只關(guān)注“鳥會(huì)不會(huì)飛”,但如果我們還關(guān)注“鳥會(huì)不會(huì)叫”,那這個(gè)時(shí)候,我們又 該如何設(shè)計(jì)類之間的繼承關(guān)系呢?是否會(huì)飛?是否會(huì)叫??jī)蓚€(gè)行為搭配起來會(huì)產(chǎn)生四種情況:會(huì)飛會(huì)叫、不會(huì)飛會(huì)叫、會(huì)飛不會(huì)叫、不會(huì)飛不會(huì)叫。如果我們繼續(xù)沿用剛才的設(shè)計(jì)思路,那就需要再定義四

6、個(gè)抽象類(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)。如果我們還需要考慮“是否會(huì)下蛋”這樣一個(gè)行為,那估計(jì)就要組合爆炸了。類的繼承層次會(huì)越來越深、繼承關(guān)系會(huì)越來越復(fù)雜。而這種層次很深、很復(fù)雜的繼承關(guān)系,一方面,會(huì)導(dǎo)致代碼的可讀性變差。因?yàn)槲覀円闱宄硞€(gè)類具有哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼一直追溯到最頂層父類的代碼。另一方面,這也破壞了類的封裝特性,將父類的實(shí)現(xiàn)細(xì)節(jié)暴露

7、給了子類。子類的實(shí)現(xiàn)依賴父類的實(shí)現(xiàn),兩者高度耦合,一旦父類代碼修改,就會(huì)影響所有子類的邏輯??傊?,繼承最大的問題就在于:繼承層次過深、繼承關(guān)系過于復(fù)雜會(huì)影響到代碼的可讀性和可維護(hù)性。這也是為什么我們不推薦使用繼承。那剛剛例子中繼承存在的問題,我們又該如何來解決呢?你可以先自己思考一下,再聽我下面的講解。組合相比繼承有哪些優(yōu)勢(shì)?實(shí)際上,我們可以利用組合(composition)、接口、委托(delegation)三個(gè)技術(shù)手 段,一塊兒來解決剛剛繼承存在的問題。我們前面講到接口的時(shí)候說過,接口表示具有某種行為特性。針對(duì)“會(huì)飛”這樣一個(gè)行為特性,我們可以定義一個(gè) Flyable 接口,只讓會(huì)飛的鳥去

8、實(shí)現(xiàn)這個(gè)接口。對(duì)于會(huì)叫、會(huì)下蛋這些行為特性,我們可以類似地定義 Tweetable 接口、EggLayable 接口。我們將這個(gè)設(shè)計(jì)思路翻譯成 Java 代碼的話,就是下面這個(gè)樣子:復(fù)制代碼123456789101112131415161718192021222324publicvoidpublicvoidpublicvoidpublicinterfacefly();Flyable interfacetweet();Tweetable interfacelayEgg();EggLayable EggLayable / 鴕鳥class Ostrich implements Tweetable,/

9、. 省略其他屬性和方法.Overridepublic void tweet() /. Overridepublic void layEgg() /. public class Sparrow impelents Flayable, Tweetable, EggLayable / 麻雀/. 省略其他屬性和方法.Overridepublic void Override public void Overridepublic voidfly() /. tweet() /. layEgg() /. 25不過,我們知道,接口只聲明方法,不定義實(shí)現(xiàn)。也就是說,每個(gè)會(huì)下蛋的鳥都要實(shí)現(xiàn)一遍layEgg() 方法

10、,并且實(shí)現(xiàn)邏輯是一樣的,這就會(huì)導(dǎo)致代碼重復(fù)的問題。那這個(gè)問題又該如何解決呢?我們可以針對(duì)三個(gè)接口再定義三個(gè)實(shí)現(xiàn)類,它們分別是:實(shí)現(xiàn)了 fly() 方法的 FlyAbility類、實(shí)現(xiàn)了 tweet() 方法的 TweetAbility 類、實(shí)現(xiàn)了 layEgg() 方法的 EggLayAbility 類。然后,通過組合和委托技術(shù)來消除代碼重復(fù)。具體的代碼實(shí)現(xiàn)如下所示:復(fù)制代碼12345678910111213141516171819202122public interface Flyable void fly();public class FlyAbility implements Flyab

11、le Overridepublic void fly() /. / 省 略 Tweetable/TweetAbility/EggLayable/EggLayAbilitypublic class Ostrich implements Tweetable, EggLayable / 鴕鳥private TweetAbility tweetAbility = new TweetAbility(); / 組合private EggLayAbility eggLayAbility = new EggLayAbility(); / 組合/. 省略其他屬性和方法.Overridepublic void t

12、weet() tweetAbility.tweet(); / 委 托Overridepublic void layEgg() eggLayAbility.layEgg(); / 委 托我們知道繼承主要有三個(gè)作用:表示 is-a 關(guān)系,支持多態(tài)特性,代碼復(fù)用。而這三個(gè)作用都可以通過其他技術(shù)手段來達(dá)成。比如 is-a 關(guān)系,我們可以通過組合和接口的 has-a 關(guān)系來替代;多態(tài)特性我們可以利用接口來實(shí)現(xiàn);代碼復(fù)用我們可以通過組合和委托來實(shí)現(xiàn)。所以,從理論上講,通過組合、接口、委托三個(gè)技術(shù)手段,我們完全可以替換掉繼承,在項(xiàng)目中不用或者少用繼承關(guān)系,特別是一些復(fù)雜的繼承關(guān)系。如何判斷該用組合還是繼承?

13、盡管我們鼓勵(lì)多用組合少用繼承,但組合也并不是完美的,繼承也并非一無是處。從上面的例子來看,繼承改寫成組合意味著要做更細(xì)粒度的類的拆分。這也就意味著,我們要定義更 多的類和接口。類和接口的增多也就或多或少地增加代碼的復(fù)雜程度和維護(hù)成本。所以,在 實(shí)際的項(xiàng)目開發(fā)中,我們還是要根據(jù)具體的情況,來具體選擇該用繼承還是組合。如果類之間的繼承結(jié)構(gòu)穩(wěn)定(不會(huì)輕易改變),繼承層次比較淺(比如,最多有兩層繼承關(guān)系),繼承關(guān)系不復(fù)雜,我們就可以大膽地使用繼承。反之,系統(tǒng)越不穩(wěn)定,繼承層次很深,繼承關(guān)系復(fù)雜,我們就盡量使用組合來替代繼承。除此之外,還有一些設(shè)計(jì)模式會(huì)固定使用繼承或者組合。比如,裝飾者模式(decor

14、atorpattern)、策略模式(strategy pattern)、組合模式(composite pattern)等都使用了組合關(guān)系,而模板模式(template pattern)使用了繼承關(guān)系。前面我們講到繼承可以實(shí)現(xiàn)代碼復(fù)用。利用繼承特性,我們把相同的屬性和方法,抽取出來,定義到父類中。子類復(fù)用父類中的屬性和方法,達(dá)到代碼復(fù)用的目的。但是,有的時(shí)候,從業(yè)務(wù)含義上,A 類和 B 類并不一定具有繼承關(guān)系。比如,Crawler 類和PageAnalyzer 類,它們都用到了 URL 拼接和分割的功能,但并不具有繼承關(guān)系(既不是 父子關(guān)系,也不是兄弟關(guān)系)。僅僅為了代碼復(fù)用,生硬地抽象出一個(gè)父

15、類出來,會(huì)影響到代碼的可讀性。如果不熟悉背后設(shè)計(jì)思路的同事,發(fā)現(xiàn) Crawler 類和 PageAnalyzer 類繼承同一個(gè)父類,而父類中定義的卻只是 URL 相關(guān)的操作,會(huì)覺得這個(gè)代碼寫得莫名其妙,理解不了。這個(gè)時(shí)候,使用組合就更加合理、更加靈活。具體的代碼實(shí)現(xiàn)如下所示:復(fù)制代碼123456789101112131415public class Url /. 省略屬性和方法public class Crawler private Url url; public Crawler() this.url = new/./ 組 合Url();public class PageAnalyzer pr

16、ivate Url url; / 組合public PageAnalyzer() 16171819this.url = new Url();/.還有一些特殊的場(chǎng)景要求我們必須使用繼承。如果你不能改變一個(gè)函數(shù)的入?yún)㈩愋?,而入?yún)⒂址墙涌?,為了支持多態(tài),只能采用繼承來實(shí)現(xiàn)。比如下面這樣一段代碼,其中FeignClient是一個(gè)外部類,我們沒有權(quán)限去修改這部分代碼,但是我們希望能重寫這個(gè)類 在運(yùn)行時(shí)執(zhí)行的 encode() 函數(shù)。這個(gè)時(shí)候,我們只能采用繼承來實(shí)現(xiàn)了。盡管有些人說,要杜絕繼承,100% 用組合代替繼承,但是我的觀點(diǎn)沒那么!之所以“多用組合少用繼承”這個(gè)喊得這么響,只是因?yàn)?,長(zhǎng)期以來,我們

17、過度使用繼承。還是那句話,組合并不完美,繼承也不是一無是處。只要我們控制好它們的副作用、發(fā)揮它們各自的優(yōu)勢(shì),在不同的場(chǎng)合下,恰當(dāng)?shù)剡x擇使用繼承還是組合,這才是我們所追求的境界。重點(diǎn)回顧到此,今天的內(nèi)容就講完了。我們一塊兒來回顧一下,你需要重點(diǎn)掌握的知識(shí)點(diǎn)。復(fù)制代碼1 public class FeignClient / feighn client 框 架 代 碼2/. 省略其他代碼.3public void encode(String url) /. 4 56 public void demofunction(FeignClient feignClient) 7/.8feignClient.e

18、ncode(url);9/.10 1112 public class CustomizedFeignClient extends FeignClient 13 Override14 public void encode(String url) /. 重寫 encode 的實(shí)現(xiàn).15 1617 / 調(diào) 用18 FeignClient client = new CustomizedFeignClient();19 demofunction(client);1. 為什么不推薦使用繼承?繼承是面向?qū)ο蟮乃拇筇匦灾唬脕肀硎绢愔g的 is-a 關(guān)系,可以解決代碼復(fù)用的問題。雖然繼承有諸多作用,但繼承層

19、次過深、過復(fù)雜,也會(huì)影響到代碼的可維護(hù)性。在這種情況下,我們應(yīng)該盡量少用,甚至不用繼承。2. 組合相比繼承有哪些優(yōu)勢(shì)?繼承主要有三個(gè)作用:表示 is-a 關(guān)系,支持多態(tài)特性,代碼復(fù)用。而這三個(gè)作用都可以通過組合、接口、委托三個(gè)技術(shù)手段來達(dá)成。除此之外,利用組合還能解決層次過深、過復(fù)雜的繼承關(guān)系影響代碼可維護(hù)性的問題。3. 如何判斷該用組合還是繼承?盡管我們鼓勵(lì)多用組合少用繼承,但組合也并不是完美的,繼承也并非一無是處。在實(shí)際的項(xiàng)目開發(fā)中,我們還是要根據(jù)具體的情況,來選擇該用繼承還是組合。如果類之間的繼承結(jié)構(gòu)穩(wěn)定,層次比較淺,關(guān)系不復(fù)雜,我們就可以大膽地使用繼承。反之,我們就盡量使用組合來替代繼

20、承。除此之外,還有一些設(shè)計(jì)模式、特殊的應(yīng)用場(chǎng)景,會(huì)固定使用繼承或者組合。課堂討論我們?cè)诨?MVC 架構(gòu)開發(fā) Web 應(yīng)用的時(shí)候,經(jīng)常會(huì)在數(shù)據(jù)庫層定義 Entity,在 Service 業(yè)務(wù)層定義 BO(Business Object),在 Controller 接口層定義 VO(View Object)。大部分情況下,Entity、BO、VO 三者之間的代碼有很大重復(fù),但又不完全相同。我們?cè)撊?何處理 Entity、BO、VO 代碼重復(fù)的問題呢?歡迎在留言區(qū)寫下你的答案,和同學(xué)一起交流和分享。如果有收獲,也歡迎你把這篇文章分享給你的朋友。 版權(quán)歸極客邦科技所有,未經(jīng)許可不得傳播售賣。 頁面已

21、增加防盜追蹤,如有侵權(quán)極客邦將依法追究其法律責(zé)任。上一篇09 | 理論六:為什么基于接口而非實(shí)現(xiàn)編程?有必要為每個(gè)類都定義接口嗎?精選留言 (55)探索無止境2019-11-25我個(gè)人感覺VO和BO都會(huì)采用組合entity的方式,老師是否可以在下一節(jié)課課聊聊上節(jié)課留下的思考題,您的處理方式?展開寫留言540Paul Shan2019-11-25我的觀點(diǎn)比較1.,用接口,組合和委托代替繼承。原因如下:人無法預(yù)知未來,現(xiàn)在比較穩(wěn)定的類繼承關(guān)系將來未必穩(wěn)定。2.兩種設(shè)計(jì)之間的選擇耗費(fèi)資源,每次都要為這個(gè)問題拿捏一下,甚至爭(zhēng)論一下,不如把爭(zhēng)論放在業(yè)務(wù)邏輯的實(shí)現(xiàn)上。3.相對(duì)于接口+組合+委托增加的復(fù)雜度

22、,代碼統(tǒng)一成接口+組合+委托帶來的好處更多,展開128Geek2019-11-25打卡看完之后有種感覺,我們平常寫的spring的依賴注入這種形式,是不是就是跟組合,委托這種模式啊展開39滄月丶下2019-11-25public class FeignClient / feighn client框架代碼feighn - feign 勘誤展開2傲慢與偏執(zhí),2019-11-25我只有在該類需要更細(xì)化詳情信息的時(shí)候會(huì)組合詳情類的list 看了這節(jié)課后 受益匪淺2李湘河2019-11-25現(xiàn)代軍事中的開發(fā)都在追求模塊化開發(fā),這樣裝備之間通用性更強(qiáng),戰(zhàn)損時(shí)隨時(shí)可以替換掉損壞的模塊,這樣又可以重新作戰(zhàn),當(dāng)

23、要增強(qiáng)坦克某一部分的性能時(shí),僅改進(jìn)對(duì)應(yīng)的模塊就行,感覺很像組合的思想。就像文中說的,對(duì)于結(jié)構(gòu)穩(wěn)定,層次淺的地方完全可以用繼承,或者說可以局部用繼承,比如VO層,對(duì)于用戶檢驗(yàn),分頁等都可以抽象出來展開2辣么大2019-11-25Entity,也稱為DO (Data Object),與數(shù)據(jù)庫表結(jié)構(gòu)一一對(duì)應(yīng),到過DAO層向上傳輸對(duì)象,應(yīng)獨(dú)立為一個(gè)類。BO,VO 可以采用繼承或者組合的方式,復(fù)用DO的代碼。展開2黃林晴2019-11-25打卡老師好,今天剛用繼承優(yōu)化了代碼臃腫的問題,但是感覺好奇怪,請(qǐng)老師指導(dǎo):所有的消息都會(huì)先到一個(gè)A類中,在A類中,根據(jù)消息類型,比如類型1 2 3 4去處理不同的業(yè)務(wù)

24、,每一類的業(yè)務(wù)都需要處理對(duì)應(yīng)數(shù)據(jù),原本隨著消息類型的增加不斷往這個(gè)A類中擴(kuò)展代碼,導(dǎo)致不好維護(hù),所以我對(duì)每個(gè)業(yè)務(wù)模型建對(duì)應(yīng)的類繼承這個(gè)A類,在A類中將消息展開72啦啦啦2019-11-25?php/叫interface jiaoablepublic function jiao();展開1Jxin2019-11-251.bovo和entity三個(gè)命名在現(xiàn)在面向服務(wù)而非頁面的后端編程,并不合適。2.這里最好用組合。entity是最小的實(shí)體單元,bo可能面對(duì)多個(gè)entity聚合,vo可能面對(duì)多個(gè)bo聚合,這種場(chǎng)景下,顯然組合更適合。雖然也存在entity和bo一對(duì)一的場(chǎng)景,或者bo 中只有一個(gè)主en

25、tity的場(chǎng)景,這種場(chǎng)景用繼承倒也不為過。但是,為了套路單一,減少閱讀思考,統(tǒng)一組合便是,沒必要再引入繼承。展開1睡覺2019-11-25GO完全摒棄了繼承,在語法上只有組合,接口之間也可以組合(這也是官方鼓勵(lì)的做法)。1tt2019-11-25談?wù)剬?duì)下面一段話的理解:“我們知道繼承主要有三個(gè)作用:表示 is-a 關(guān)系,支持多態(tài)特性,代碼復(fù)用。而這三個(gè)作用都可以通過其他技術(shù)手段來達(dá)成。比如 is-a 關(guān)系,我們可以通過組合和接口的 has-a 展開1辣么大2019-11-25很多同學(xué)提出復(fù)用Entity(DO),我有不同意見:若修改DO,可能會(huì)影響到BO和VO。我們都知道DO對(duì)應(yīng)數(shù)據(jù)表,如PersonDO類有id,age,name。若現(xiàn)在需求改變,age要從政府系統(tǒng)獲取,原有的Person表要?jiǎng)h除age字段,相應(yīng)的DO類就要修改,UI仍然顯示person.age。BO、VO有如果使用了DO就會(huì)受到影響。為

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論