設(shè)計(jì)模式系列之組合模式及其在JDK和MyBatis源碼中的運(yùn)用詳解
組合模式及其在JDK源碼中的運(yùn)用 前言組合和聚合什么是組合模式示例透明組合模式透明組合模式的缺陷安全組合模式 組合模式角色組合模式在JDK源碼中的體現(xiàn)組合模式應(yīng)用場(chǎng)景享元模式優(yōu)缺點(diǎn)總結(jié)
前言
本文主要會(huì)講述組合模式的用法,并會(huì)結(jié)合在JDK和MyBatis源碼中的運(yùn)用來(lái)進(jìn)一步理解組合模式。
在編碼原則中,有一條是:多用組合,少用繼承。當(dāng)然這里的組合和我們今天要講的組合模式并不等價(jià),這里的組合其實(shí)就是一種聚合,那么聚合和組合有什么區(qū)別呢?
組合和聚合
人在一起叫團(tuán)伙,心在一起叫團(tuán)隊(duì)。用這句話來(lái)詮釋組合與聚合的區(qū)別是相對(duì)恰當(dāng)?shù)摹?/p>
聚合就是說(shuō)各個(gè)對(duì)象聚合在一起工作,但是我沒有你也行,我照樣可以正常運(yùn)行。但是組合呢,關(guān)系就比較密切,組合中的各個(gè)對(duì)象之間組成了一個(gè)整體,缺少了某一個(gè)對(duì)象就不能正常運(yùn)行或者說(shuō)功能會(huì)有很大缺陷。
也就是說(shuō)聚合對(duì)象不具備相同生命周期,而組合的對(duì)象具有相同的生命周期
舉個(gè)例子:
比如說(shuō)電腦和U盤就是聚合,而電腦顯示器和主機(jī)就是組合。
什么是組合模式
組合模式(Composite Pattern)也稱之為整體-部分(Part-Whole)模式。組合模式的核心是通過(guò)將單個(gè)對(duì)象(葉子節(jié)點(diǎn))和組合對(duì)象(樹枝節(jié)點(diǎn))用相同的接口進(jìn)行表示,使得單個(gè)對(duì)象和組合對(duì)象的使用具有一致性。組合模式屬于結(jié)構(gòu)型模式。
組合模式一般用來(lái)描述整體與部分的關(guān)系,它將對(duì)象組織到樹形結(jié)構(gòu)中,最頂層的節(jié)點(diǎn)稱為根節(jié)點(diǎn),根節(jié)點(diǎn)下面可以包含樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn),樹枝節(jié)點(diǎn)下面又可以包含樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)如下圖所示:
講了這么多,感覺有點(diǎn)抽象,所以依然是老規(guī)矩:Talk is cheap,Show you the code。
示例
組合模式有兩種寫法,分別是透明模式和安全模式。下面我們就以高考的科目為例來(lái)看看組合模式是如何體現(xiàn)在代碼中的
透明組合模式
1、首先建立一個(gè)頂層的抽象科目類,這個(gè)類中定義了三個(gè)通用操作方法,但是均默認(rèn)不支持操作
package com.zwx.design.pattern.composite.transparency; /** * 頂層抽象組件 */ public abstract class GkAbstractCourse { public void addChild(GkAbstractCourse course){ System.out.println("不支持添加操作"); } public String getName() throws Exception { throw new Exception("不支持獲取名稱"); } public void info() throws Exception{ throw new Exception("不支持查詢信息操作"); } }
PS:這個(gè)類中的公共方法之所以不定義為抽象方法的原因是因?yàn)榧偃缍x為抽象方法,那么所有的子類都必須重寫父類方法,這樣體現(xiàn)不出差異性。而這種通過(guò)拋異常的方式,如果子類需要用到的功能就重寫覆蓋父類方法即可。
2、新建一個(gè)普通科目類繼承通用科目抽象類,這個(gè)類作為葉子節(jié)點(diǎn),沒有重寫addChild方法,也就是這個(gè)類屬于葉子節(jié)點(diǎn),不支持添加子節(jié)點(diǎn):
package com.zwx.design.pattern.composite.transparency; /** * 普通科目類(葉子節(jié)點(diǎn)) */ public class CommonCource extends GkAbstractCourse { private String name;//課程名稱 private String score;//課程分?jǐn)?shù) public CommonCource(String name, String score) { this.name = name; this.score = score; } @Override public String getName(){ return this.name; } @Override public void info() { System.out.println("課程:" + this.name + ",分?jǐn)?shù):" + score); } }
3、建立一個(gè)具有層級(jí)的節(jié)點(diǎn),三個(gè)方法都重寫了,支持添加子節(jié)點(diǎn),這個(gè)類里面為了方便打印的時(shí)候看出層級(jí)關(guān)系,所以我定義了一個(gè)層級(jí)屬性。
package com.zwx.design.pattern.composite.transparency; import java.util.ArrayList; import java.util.List; /** * 樹枝節(jié)點(diǎn) */ public class LevelCource extends GkAbstractCourse{ private List<GkAbstractCourse> courseList = new ArrayList<>(); private String name; private int level; public LevelCource(String name, int level) { this.name = name; this.level = level; } @Override public void addChild(GkAbstractCourse course) { courseList.add(course); } @Override public String getName(){ return this.name; } @Override public void info() throws Exception { System.out.println("課程:" + this.name); for (GkAbstractCourse course : courseList){ for (int i=0;i<level;i++){ System.out.print(" "); } System.out.print(">"); course.info(); } } }
4、建立一個(gè)測(cè)試類來(lái)測(cè)試一下:
package com.zwx.design.pattern.composite.transparency; public class TestTransparency { public static void main(String[] args) throws Exception { GkAbstractCourse ywCourse = new CommonCource("語(yǔ)文","150"); GkAbstractCourse sxCourse = new CommonCource("數(shù)學(xué)","150"); GkAbstractCourse yyCourse = new CommonCource("英語(yǔ)","150"); GkAbstractCourse wlCourse = new CommonCource("物理","110"); GkAbstractCourse hxCourse = new CommonCource("化學(xué)","100"); GkAbstractCourse swCourse = new CommonCource("生物","90"); GkAbstractCourse lzCourse = new LevelCource("理綜",2); lzCourse.addChild(wlCourse); lzCourse.addChild(hxCourse); lzCourse.addChild(swCourse); GkAbstractCourse gkCourse = new LevelCource("理科高考科目",1); gkCourse.addChild(ywCourse); gkCourse.addChild(sxCourse); gkCourse.addChild(yyCourse); gkCourse.addChild(lzCourse); gkCourse.info(); } }
輸出結(jié)果:
課程:理科高考科目
>課程:語(yǔ)文,分?jǐn)?shù):150
>課程:數(shù)學(xué),分?jǐn)?shù):150
>課程:英語(yǔ),分?jǐn)?shù):150
>課程:理綜
>課程:物理,分?jǐn)?shù):110
>課程:化學(xué),分?jǐn)?shù):100
>課程:生物,分?jǐn)?shù):90
這里如果用普通科目去調(diào)用add方法就會(huì)拋出異常,假如上面調(diào)用:
swCourse.addChild(ywCourse);
會(huì)輸出
不支持添加操作
因?yàn)樵谄胀颇款惱锩娌]有重寫addChild方法。
透明組合模式的缺陷
透明模式的特點(diǎn)就是將組合對(duì)象所有的公共方法都定義在了抽象組件內(nèi),這樣做的好處是客戶端無(wú)需分辨當(dāng)前對(duì)象是屬于樹枝節(jié)點(diǎn)還是葉子節(jié)點(diǎn),因?yàn)樗鼈兙邆淞送耆恢碌慕涌?,不過(guò)缺點(diǎn)就是葉子節(jié)點(diǎn)得到到了一些不屬于它的方法,比如上面的addChild方法,這違背了接口隔離性原則。
安全組合模式
安全組合模式只是規(guī)定了系統(tǒng)各個(gè)層次的最基礎(chǔ)的一致性行為,而把組合(樹節(jié)點(diǎn))本身的方法(如樹枝節(jié)點(diǎn)管理子類的addChild等方法)放到自身當(dāng)中。
1、首先還是建立一個(gè)頂層的抽象根節(jié)點(diǎn)(這里面只定義了一個(gè)通用的抽象info方法):
package com.zwx.design.pattern.composite.safe; package com.zwx.design.pattern.composite.safe; /** * 頂層抽象組件 */ public abstract class GkAbstractCourse { protected String name; protected String score; public GkAbstractCourse(String name, String score) { this.name = name; this.score = score; } public abstract void info(); }
2、建立一個(gè)葉子節(jié)點(diǎn)(這里只是重寫了info方法,沒有定義其他特有方法):
package com.zwx.design.pattern.composite.safe; /** * 葉子節(jié)點(diǎn) */ public class CommonCource extends GkAbstractCourse { public CommonCource(String name,String score) { super(name,score); } @Override public void info() { System.out.println("課程:" + this.name + ",分?jǐn)?shù):" + this.score); } }
3、定義一個(gè)樹枝節(jié)點(diǎn)(這個(gè)類當(dāng)中定義了一個(gè)樹枝特有的方法addChild):
package com.zwx.design.pattern.composite.safe; import java.util.ArrayList; import java.util.List; /** * 樹枝節(jié)點(diǎn) */ public class LevelCource extends GkAbstractCourse{ private List<GkAbstractCourse> courseList = new ArrayList<>(); private int level; public LevelCource(String name, String score,int level) { super(name,score); this.level = level; } public void addChild(GkAbstractCourse course) { courseList.add(course); } @Override public void info() { System.out.println("課程:" + this.name + ",分?jǐn)?shù):" + this.score); for (GkAbstractCourse course : courseList){ for (int i=0;i<level;i++){ System.out.print(" "); } System.out.print(">"); course.info(); } } }
4、新建測(cè)試類來(lái)測(cè)試:
package com.zwx.design.pattern.composite.safe; public class TestSafe { public static void main(String[] args) throws Exception { CommonCource ywCourse = new CommonCource("語(yǔ)文","150"); CommonCource sxCourse = new CommonCource("數(shù)學(xué)","150"); CommonCource yyCourse = new CommonCource("英語(yǔ)","150"); CommonCource wlCourse = new CommonCource("物理","110"); CommonCource hxCourse = new CommonCource("化學(xué)","100"); CommonCource swCourse = new CommonCource("生物","90"); LevelCource lzCourse = new LevelCource("理綜","300",2); lzCourse.addChild(wlCourse); lzCourse.addChild(hxCourse); lzCourse.addChild(swCourse); LevelCource gkCourse = new LevelCource("理科高考","750",1); gkCourse.addChild(ywCourse); gkCourse.addChild(sxCourse); gkCourse.addChild(yyCourse); gkCourse.addChild(lzCourse); gkCourse.info(); } }
輸出結(jié)果為:
課程:理科高考,分?jǐn)?shù):750
>課程:語(yǔ)文,分?jǐn)?shù):150
>課程:數(shù)學(xué),分?jǐn)?shù):150
>課程:英語(yǔ),分?jǐn)?shù):150
>課程:理綜,分?jǐn)?shù):300
>課程:物理,分?jǐn)?shù):110
>課程:化學(xué),分?jǐn)?shù):100
>課程:生物,分?jǐn)?shù):90
這里和透明方式不一樣,葉子節(jié)點(diǎn)不具備addChild功能,所以無(wú)法調(diào)用,而上面的示例中時(shí)可以被調(diào)用,但是調(diào)用之后顯示不支持,這就是這兩種寫法最大的區(qū)別。
組合模式角色
從上面示例中,可以看到組合模式包含了以下三個(gè)角色:
- 抽象根節(jié)點(diǎn)(Component):定義系統(tǒng)各層次對(duì)象的公有屬性和方法,可以預(yù)先定義一些默認(rèn)行為和屬性。
- 樹枝節(jié)點(diǎn)(Composite):定義樹枝節(jié)點(diǎn)的行為,存儲(chǔ)子節(jié)點(diǎn),組合樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)形成一個(gè)樹形結(jié)構(gòu)。
- 葉子節(jié)點(diǎn)(Leaf):是系統(tǒng)遍歷層次中的最小單位,下面沒有子節(jié)點(diǎn)。
組合模式在JDK源碼中的體現(xiàn)
1、HashMap
HashMap中有一個(gè)putAll方法,參數(shù)是一個(gè)Map,這就是一種組合模式的體現(xiàn):
另外還有ArrayList中的addAll方法也是一樣。
2、MyBatis中有一個(gè)SqlNode接口,下面很多一級(jí)標(biāo)簽:
然后一級(jí)標(biāo)簽下面又有二級(jí)標(biāo)簽(這就是組合模式的體現(xiàn)):
組合模式應(yīng)用場(chǎng)景
組合模式一般應(yīng)用在有層級(jí)關(guān)系的場(chǎng)景,最經(jīng)典的就是樹形菜單,文件和文件夾的管理等
享元模式優(yōu)缺點(diǎn)
優(yōu)點(diǎn):清楚的定義了分層次的復(fù)雜對(duì)象,讓客戶端可以忽略層次的差異,方便對(duì)整個(gè)層次進(jìn)行動(dòng)態(tài)控制。
缺點(diǎn):其葉子和樹枝的聲明是實(shí)現(xiàn)類而不是接口,違反了依賴倒置原則,而且組合模式會(huì)使設(shè)計(jì)更加抽象不好理解。
總結(jié)
本文主要介紹了組合模式,并介紹了普通的聚合和組合之間的區(qū)別,并通過(guò)例子詳細(xì)解釋了組合模式中的透明寫法和安全寫法的區(qū)別,最后結(jié)合在JDK和MyBatis源碼中的運(yùn)用來(lái)進(jìn)一步理解組合模式的運(yùn)用。
請(qǐng)關(guān)注我,和孤狼一起學(xué)習(xí)進(jìn)步。
到此這篇關(guān)于設(shè)計(jì)模式系列之組合模式及其在JDK和MyBatis源碼中的運(yùn)用詳解的文章就介紹到這了,更多相關(guān)組合模式在JDK和MyBatis源碼中的運(yùn)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis-Plus批量添加或修改數(shù)據(jù)的3種方式總結(jié)
使用Mybatis-plus可以很方便的實(shí)現(xiàn)批量新增和批量修改,不僅比自己寫foreach遍歷方便很多,而且性能也更加優(yōu)秀,下面這篇文章主要給大家介紹了關(guān)于Mybatis-Plus批量添加或修改數(shù)據(jù)的3種方式,需要的朋友可以參考下2023-05-05Java封裝的實(shí)現(xiàn)訪問(wèn)限定符、包
封裝就是將數(shù)據(jù)和操作數(shù)據(jù)的方法進(jìn)行有機(jī)結(jié)合,隱藏對(duì)象的屬性(成員變量)和實(shí)現(xiàn)細(xì)節(jié),僅對(duì)外公開接口來(lái)和對(duì)象進(jìn)行交互,下面這篇文章主要給大家介紹了關(guān)于Java封裝實(shí)現(xiàn)訪問(wèn)限定符、包的相關(guān)資料2022-08-08Java.lang.Long.parseLong()方法詳解及示例
這個(gè)java.lang.Long.parseLong(String s) 方法解析字符串參數(shù)s作為有符號(hào)十進(jìn)制長(zhǎng),下面這篇文章主要給大家介紹了關(guān)于Java.lang.Long.parseLong()方法詳解及示例的相關(guān)資料,需要的朋友可以參考下2023-01-01Spring Security 中如何讓上級(jí)擁有下級(jí)的所有權(quán)限(案例分析)
這篇文章主要介紹了Spring Security 中如何讓上級(jí)擁有下級(jí)的所有權(quán)限,本文通過(guò)案例分析給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09springboot實(shí)現(xiàn)多模塊項(xiàng)目添加一新模塊
這篇文章主要介紹了springboot實(shí)現(xiàn)多模塊項(xiàng)目添加一新模塊,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-02-02關(guān)于Idea創(chuàng)建Java項(xiàng)目并引入lombok包的問(wèn)題(lombok.jar包免費(fèi)下載)
很多朋友遇到當(dāng)idea創(chuàng)建java項(xiàng)目時(shí),命名安裝了lombok插件卻不能使用注解,原因有兩個(gè)大家可以參考下本文,本文對(duì)每種原因分析給出了解決方案,需要的朋友參考下吧2021-06-06Spring啟動(dòng)后獲取所有擁有特定注解的Bean實(shí)例代碼
這篇文章主要介紹了Spring啟動(dòng)后獲取所有擁有特定注解的Bean實(shí)例代碼,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02