設(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ì)象聚合在一起工作,但是我沒(méi)有你也行,我照樣可以正常運(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)如下圖所示:

講了這么多,感覺(jué)有點(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),沒(méi)有重寫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)樵谄胀颇款惱锩娌](méi)有重寫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方法,沒(méi)有定義其他特有方法):
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)遍歷層次中的最小單位,下面沒(méi)有子節(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)文章
基于SpringBoot開機(jī)啟動(dòng)與@Order注解
這篇文章主要介紹了SpringBoot開機(jī)啟動(dòng)與@Order注解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
SpringBoot用JdbcTemplates訪問(wèn)Mysql實(shí)例代碼
本篇文章主要介紹了SpringBoot用JdbcTemplates訪問(wèn)Mysql實(shí)例代碼,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-05-05
Java實(shí)現(xiàn)Jar文件的遍歷復(fù)制與文件追加
這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)Jar文件的遍歷復(fù)制與文件追加功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11
Java中對(duì)null進(jìn)行強(qiáng)制類型轉(zhuǎn)換的方法
小編對(duì)null進(jìn)行強(qiáng)轉(zhuǎn)會(huì)不會(huì)拋錯(cuò),非常的好奇,下面小編通過(guò)實(shí)例代碼給大家介紹Java中對(duì)null進(jìn)行強(qiáng)制類型轉(zhuǎn)換的方法,感興趣的朋友參考下吧2018-09-09
詳解@ConfigurationProperties如何裝載到Spring容器中
這篇文章主要為大家詳細(xì)介紹了@ConfigurationProperties該如何裝載到Spring容器中,文中的示例代碼講解詳細(xì),需要的小伙伴可以參考一下2023-07-07
Java套接字(Socket)網(wǎng)絡(luò)編程入門
這篇文章主要介紹了Java套接字(Socket)網(wǎng)絡(luò)編程入門,Socket可以理解為是對(duì)TCP/IP協(xié)議的抽象,需要的朋友可以參考下2015-10-10
利用Java實(shí)現(xiàn)簡(jiǎn)單的詞法分析器實(shí)例代碼
眾所周知編譯原理中的詞法分析算是很重要的一個(gè)部分,原理比較簡(jiǎn)單,不過(guò)網(wǎng)上大部分都是用C語(yǔ)言或者C++來(lái)編寫,因?yàn)樽罱趯W(xué)習(xí)Java,故用Java語(yǔ)言實(shí)現(xiàn)了簡(jiǎn)單的詞法分析器。感興趣的朋友們可以參考借鑒,下面來(lái)一起看看吧。2016-12-12
Java使用Flyway實(shí)現(xiàn)數(shù)據(jù)庫(kù)版本控制的技術(shù)指南
在現(xiàn)代應(yīng)用開發(fā)中,數(shù)據(jù)庫(kù)結(jié)構(gòu)經(jīng)常隨著業(yè)務(wù)需求不斷演變,使用手動(dòng)SQL腳本管理數(shù)據(jù)庫(kù)版本,不僅容易出現(xiàn)錯(cuò)誤,還難以跟蹤和回滾,Flyway是一個(gè)強(qiáng)大的數(shù)據(jù)庫(kù)遷移工具,能夠幫助開發(fā)者高效管理和自動(dòng)化數(shù)據(jù)庫(kù)的版本控制,本文將介紹Flyway的基本功能及其在SpringBoot項(xiàng)目中的實(shí)踐2025-02-02
Spring Boot 中的任務(wù)執(zhí)行器基本概念及使用方法
務(wù)執(zhí)行器是 Spring Boot 中的一個(gè)非常實(shí)用的模塊,它可以簡(jiǎn)化異步任務(wù)的開發(fā)和管理,在本文中,我們介紹了任務(wù)執(zhí)行器的基本概念和使用方法,以及一個(gè)完整的示例代碼,需要的朋友可以參考下2023-07-07

