組合模式及其在JDK原始碼中的運用
- 前言
- 組合和聚合
- 什么是組合模式
- 示例
- 透明組合模式
- 透明組合模式的缺陷
- 安全組合模式
- 組合模式角色
- 組合模式在JDK原始碼中的體現
- 組合模式應用場景
- 享元模式優缺點
- 總結
前言
本文主要會講述組合模式的用法,并會結合在JDK和MyBatis原始碼中的運用來進一步理解組合模式,
在編碼原則中,有一條是:多用組合,少用繼承,當然這里的組合和我們今天要講的組合模式并不等價,這里的組合其實就是一種聚合,那么聚合和組合有什么區別呢?
組合和聚合
人在一起叫團伙,心在一起叫團隊,用這句話來詮釋組合與聚合的區別是相對恰當的,
聚合就是說各個物件聚合在一起作業,但是我沒有你也行,我照樣可以正常運行,但是組合呢,關系就比較密切,組合中的各個物件之間組成了一個整體,缺少了某一個物件就不能正常運行或者說功能會有很大缺陷,
也就是說聚合物件不具備相同生命周期,而組合的物件具有相同的生命周期
舉個例子:
比如說電腦和U盤就是聚合,而電腦顯示幕和主機就是組合,
什么是組合模式
組合模式(Composite Pattern)也稱之為整體-部分(Part-Whole)模式,組合模式的核心是通過將單個物件(葉子節點)和組合物件(樹枝節點)用相同的介面進行表示,使得單個物件和組合物件的使用具有一致性,組合模式屬于結構型模式,
組合模式一般用來描述整體與部分的關系,它將物件組織到樹形結構中,最頂層的節點稱為根節點,根節點下面可以包含樹枝節點和葉子節點,樹枝節點下面又可以包含樹枝節點和葉子節點如下圖所示:

講了這么多,感覺有點抽象,所以依然是老規矩:Talk is cheap,Show you the code,
示例
組合模式有兩種寫法,分別是透明模式和安全模式,下面我們就以高考的科目為例來看看組合模式是如何體現在代碼中的
透明組合模式
1、首先建立一個頂層的抽象科目類,這個類中定義了三個通用操作方法,但是均默認不支持操作
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:這個類中的公共方法之所以不定義為抽象方法的原因是因為假如定義為抽象方法,那么所有的子類都必須重寫父類方法,這樣體現不出差異性,而這種通過拋例外的方式,如果子類需要用到的功能就重寫覆寫父類方法即可,
2、新建一個普通科目類繼承通用科目抽象類,這個類作為葉子節點,沒有重寫addChild方法,也就是這個類屬于葉子節點,不支持添加子節點:
package com.zwx.design.pattern.composite.transparency;
/**
* 普通科目類(葉子節點)
*/
public class CommonCource extends GkAbstractCourse {
private String name;//課程名稱
private String score;//課程分數
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 + ",分數:" + score);
}
}
3、建立一個具有層級的節點,三個方法都重寫了,支持添加子節點,這個類里面為了方便列印的時候看出層級關系,所以我定義了一個層級屬性,
package com.zwx.design.pattern.composite.transparency;
import java.util.ArrayList;
import java.util.List;
/**
* 樹枝節點
*/
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、建立一個測驗類來測驗一下:
package com.zwx.design.pattern.composite.transparency;
public class TestTransparency {
public static void main(String[] args) throws Exception {
GkAbstractCourse ywCourse = new CommonCource("語文","150");
GkAbstractCourse sxCourse = new CommonCource("數學","150");
GkAbstractCourse yyCourse = new CommonCource("英語","150");
GkAbstractCourse wlCourse = new CommonCource("物理","110");
GkAbstractCourse hxCourse = new CommonCource("化學","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();
}
}
輸出結果:
課程:理科高考科目
>課程:語文,分數:150
>課程:數學,分數:150
>課程:英語,分數:150
>課程:理綜
>課程:物理,分數:110
>課程:化學,分數:100
>課程:生物,分數:90
這里如果用普通科目去呼叫add方法就會拋出例外,假如上面呼叫:
swCourse.addChild(ywCourse);
會輸出
不支持添加操作
因為在普通科目類里面并沒有重寫addChild方法,
透明組合模式的缺陷
透明模式的特點就是將組合物件所有的公共方法都定義在了抽象組件內,這樣做的好處是客戶端無需分辨當前物件是屬于樹枝節點還是葉子節點,因為它們具備了完全一致的介面,不過缺點就是葉子節點得到到了一些不屬于它的方法,比如上面的addChild方法,這違背了介面隔離性原則,
安全組合模式
安全組合模式只是規定了系統各個層次的最基礎的一致性行為,而把組合(樹節點)本身的方法(如樹枝節點管理子類的addChild等方法)放到自身當中,
1、首先還是建立一個頂層的抽象根節點(這里面只定義了一個通用的抽象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、建立一個葉子節點(這里只是重寫了info方法,沒有定義其他特有方法):
package com.zwx.design.pattern.composite.safe;
/**
* 葉子節點
*/
public class CommonCource extends GkAbstractCourse {
public CommonCource(String name,String score) {
super(name,score);
}
@Override
public void info() {
System.out.println("課程:" + this.name + ",分數:" + this.score);
}
}
3、定義一個樹枝節點(這個類當中定義了一個樹枝特有的方法addChild):
package com.zwx.design.pattern.composite.safe;
import java.util.ArrayList;
import java.util.List;
/**
* 樹枝節點
*/
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 + ",分數:" + this.score);
for (GkAbstractCourse course : courseList){
for (int i=0;i<level;i++){
System.out.print(" ");
}
System.out.print(">");
course.info();
}
}
}
4、新建測驗類來測驗:
package com.zwx.design.pattern.composite.safe;
public class TestSafe {
public static void main(String[] args) throws Exception {
CommonCource ywCourse = new CommonCource("語文","150");
CommonCource sxCourse = new CommonCource("數學","150");
CommonCource yyCourse = new CommonCource("英語","150");
CommonCource wlCourse = new CommonCource("物理","110");
CommonCource hxCourse = new CommonCource("化學","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();
}
}
輸出結果為:
課程:理科高考,分數:750
>課程:語文,分數:150
>課程:數學,分數:150
>課程:英語,分數:150
>課程:理綜,分數:300
>課程:物理,分數:110
>課程:化學,分數:100
>課程:生物,分數:90
這里和透明方式不一樣,葉子節點不具備addChild功能,所以無法呼叫,而上面的示例中時可以被呼叫,但是呼叫之后顯示不支持,這就是這兩種寫法最大的區別,
組合模式角色
從上面示例中,可以看到組合模式包含了以下三個角色:
- 抽象根節點(Component):定義系統各層次物件的公有屬性和方法,可以預先定義一些默認行為和屬性,
- 樹枝節點(Composite):定義樹枝節點的行為,存盤子節點,組合樹枝節點和葉子節點形成一個樹形結構,
- 葉子節點(Leaf):是系統遍歷層次中的最小單位,下面沒有子節點,
組合模式在JDK原始碼中的體現
- 1、HashMap
HashMap中有一個putAll方法,引數是一個Map,這就是一種組合模式的體現:
另外還有ArrayList中的addAll方法也是一樣,
- 2、MyBatis中有一個SqlNode介面,下面很多一級標簽:

然后一級標簽下面又有二級標簽(這就是組合模式的體現):

組合模式應用場景
組合模式一般應用在有層級關系的場景,最經典的就是樹形選單,檔案和檔案夾的管理等
享元模式優缺點
優點:清楚的定義了分層次的復雜物件,讓客戶端可以忽略層次的差異,方便對整個層次進行動態控制,
缺點:其葉子和樹枝的宣告是實作類而不是介面,違反了依賴倒置原則,而且組合模式會使設計更加抽象不好理解,
總結
本文主要介紹了組合模式,并介紹了普通的聚合和組合之間的區別,并通過例子詳細解釋了組合模式中的透明寫法和安全寫法的區別,最后結合在JDK和MyBatis原始碼中的運用來進一步理解組合模式的運用,
請關注我,和孤狼一起學習進步,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/101108.html
標籤:其他
