I walk very slowly, but I never walk backwards
設計模式 - 組合模式
寂然
大家好,我是寂然,本節課,我們來聊設計模式中的組合模式,老規矩,首先我們先通過一個案例需求來引入
案例示範 - 院校展示
在一個頁面中展示出學校的院系組成
一個學校會有多個學院, 一個學院有會多個專業
編寫程式,完成需求
解決方案一:一般實作
如果用最容易想到的方式,會做成繼承關系,我們定義一個學校,學校下面有各個學院子類,而每個學院有各個專業,來展示頁面,這樣實際上是站在組織大小的角度來進行分層的,類圖如下所示

但是大家考慮,學校和學院之間,真的是一個繼承關系嗎 ? 實際上我們的要求是 :在一個頁面中展示出學校的院系組成,一個學校有多個學院,一個學院有多個專業, 是以這種方案,不能很好實作的管理的操作,比如對學院專業的添加,删除,周遊等
其實更合适的一種方式是學校包含學院,學院包含專業,應該是一種組合關系,我們可以換一種思路, 把學校、學院、專業都看做是組織結構,他們之間沒有繼承的關系,而是一個樹形結構,可以更好的實作管理操作,其實這種思路就是組合模式,下面,我們一起來看下組合模式的基本介紹
基本介紹
組合模式(Composite Pattern),又叫部分整體模式,它建立了對象組的樹形結構,将對象組合成樹狀結構以表示 “整體-部分” 的層次關系
組合模式依據樹形結構來組合對象,用來表示部分以及整體層次
組合模式使得使用者對單個對象群組合對象的通路具有一緻性,即:組合能讓客戶以一緻的方式處理個别對象以及組合對象
原理類圖
下面,我們一起來看下組合模式的原理類圖,并梳理下組合模式中的角色
組合模式角色
- Component:這是組合模式中的對象聲明接口,在适當情況下,實作所有類共有的接口預設行為,用于通路和管理 Component子部件,Component 可以是抽象類或者接口
- Leaf : 在組合中表示葉子節點,葉子節點沒有子節點 ,是最末端的存放資料的結構
- Composite:非葉子節點, 用于存儲子部件,在 Component 接口中實作子部件的相關操作,比如增加(add),删除 (remove)
組合模式解決的問題
組合模式解決這樣的場景,當我們的要處理的對象可以生成一顆樹形結構,而我們要對樹上的節點和葉子進行操作時,組合模式能夠提供一緻的方式,而不用考慮它是節點還是葉子
解決方案二:組合模式
類圖展示
下面,我們使用組合模式解決院校展示問題,首先,我們先來畫原理類圖來分析思路
代碼示範
/**
* @Classname OrganizationComponent
* @Created by 寂然
* @Description I walk very slowly, but I never walk backwards
*/
public abstract class OrganizationComponent {
private String name;
private String desc; //描述
public void add(OrganizationComponent component){
//預設實作 抛出一個不支援操作的異常
throw new UnsupportedOperationException();
}
public void remove(OrganizationComponent component){
//預設實作 抛出一個不支援操作的異常
throw new UnsupportedOperationException();
}
//構造器
public OrganizationComponent(String name, String desc) {
this.name = name;
this.desc = desc;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
//展示方法,做成抽象的
public abstract void show();
}
/**
* @Classname School
* @Created by 寂然
* @Description School也就是Composite ,可以管理College
*/
public class School extends OrganizationComponent {
//構造器
public School(String name, String desc) {
super(name, desc);
}
//組合的是學院
List<OrganizationComponent> componentList = new ArrayList<>();
//重寫add方法
@Override
public void add(OrganizationComponent component) {
componentList.add(component);
}
//重寫remove方法
@Override
public void remove(OrganizationComponent component) {
componentList.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println("--- " + getName() + "---");
//周遊 componentList
for (OrganizationComponent organizationComponent : componentList) {
organizationComponent.show();
}
}
}
/**
* @Classname College
* @Created by 寂然
* @Description I walk very slowly, but I never walk backwards
*/
public class College extends OrganizationComponent {
public College(String name, String desc) {
super(name, desc);
}
//組合的專業
List<OrganizationComponent> componentList = new ArrayList<>();
//重寫add方法
@Override
public void add(OrganizationComponent component) {
//将來實際業務中,Collage和School的添加方法不相同,都有各自的業務邏輯
componentList.add(component);
}
//重寫remove方法
@Override
public void remove(OrganizationComponent component) {
componentList.remove(component);
}
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println("--- " + getName() + "---");
//周遊 componentList
for (OrganizationComponent organizationComponent : componentList) {
organizationComponent.show();
}
}
}
/**
* @Classname Major
* @Created by 寂然
* @Description 專業
*/
public class Major extends OrganizationComponent{
public Major(String name, String desc) {
super(name, desc);
}
//本案例中,Major是葉子節點,不會包含其他的,是以不需要add,remove方法
@Override
public String getName() {
return super.getName();
}
@Override
public String getDesc() {
return super.getDesc();
}
@Override
public void show() {
System.out.println(getName());
}
}
/**
* @Classname Client
* @Created by 寂然
* @Description 用戶端
*/
public class Client {
public static void main(String[] args) {
//範圍從大到小建立對象
OrganizationComponent school = new School("清華大學", "中國頂級學府");
//建立學院,并添加到學校中
OrganizationComponent computerCollege = new College("計算機學院", "計算機學院");
OrganizationComponent infoCollege = new College("資訊工程學院", "資訊工程學院");
school.add(computerCollege);
school.add(infoCollege);
//建立各個學院下面的系 并加入到學院中
computerCollege.add(new Major("計算機科學與技術","計算機科學與技術"));
computerCollege.add(new Major("軟體工程","軟體工程"));
computerCollege.add(new Major("網絡空間安全","網絡空間安全"));
infoCollege.add(new Major("通信與資訊系統","通信與資訊系統"));
infoCollege.add(new Major("信号與資訊處理","信号與資訊處理"));
infoCollege.add(new Major("資訊網絡與複雜系統","資訊網絡與複雜系統"));
school.show();
}
}
假設我不關心整個大學的,我隻關心某個學院的情況,隻需要調用對應學院的 show() 方法即可,便捷的同時非常靈活,如果我們在其中新增一個層級關系,隻需要繼承 OrganizationComponent,進行聚合,重寫裡面的業務方法即可,(add,remove,show),是以我們來聊聊使用組合模式解決案例問題的優點
組合模式優勢
- 組合模式使得用戶端代碼可以一緻地處理單個對象群組合對象,無須關心自己處理的是單個對象,還是組合對象,屏蔽了對象系統的層次差異性,使用一緻的行為控制不同層次
- 擴充性非常高,可以很友善地增加 樹枝節點 和 葉子節點 對象,并對現有類庫無侵入,滿足“開閉原則”
組合模式缺點
要求較高的抽象性,如果葉子和節點有很多差異性的話,例如很多屬性和方法都不一樣,不适合使用組合模式,而本案例院校展示,他們很多屬性和方法都是共同的,類似一個樹形結構
使用場景
- 處理類似樹形結構,具備統一行為時(如作業系統目錄結構,公司組織架構等)
- 想展現對象的部分-整體層次結構,典型的例如檔案、檔案夾的管理
(檔案系統由檔案和目錄組成,每個檔案裡裝有内容,而每個目錄的内容可以有檔案和目錄,目錄就相當于是由單個對象或組合對象組合而成,如果你想要描述的是這樣的資料結構,那麼你就可以使用組合模式 )
HashMap源碼刨析
Java的集合類 - HashMap 中就使用到了組合模式,下面我們通過一段測試代碼來進行源碼分析
public class Test {
public static void main(String[] args) {
HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("第一章","原理");
HashMap<String, String> map = new HashMap<>();
map.put("第二節","探索");
map.put("第三節","出發");
hashMap.putAll(map);
System.out.println(hashMap);
}
}
源碼流程分析
首先我們來看 Map ,這是一個接口,其實他類似組合模式中的 Component,為什麼這麼說,可以看到,裡面的put() 方法,和 putAll() 方法,有接口就有實作,接着來看 ,HashMap 實作了 Map 接口,并且實作了上述方法
* @since 1.2
*/
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
private static final long serialVersionUID = 362498820763181265L;
是以 HashMap 就是一個具體的 Composite,那葉子節點呢?
接着我們來看 HashMap 裡面的 Node ,可以看到,Node 是 HashMap 裡的一個靜态内部類
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
其實 Node 就是我們組合模式中的葉子節點,可以看到,Node裡面就不再有 put,putAll 等關鍵方法了,因為他是葉子節點,是最末端的存放資料的結構了,是以隻有一些預設方法
我們再來看 HashMap 中目标方法的實作,putVal 接受一個 K 和 V,然後以 Node 的形式放入 HashMap
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
大家是否可以大緻了解這樣一個關系,我們通過類圖來進行展示
說明
- Map 就是一個抽象的建構,即組合模式中的 Component
- HashMap 是組合模式中的非葉子節點,即 Composite ,用于存儲子部件,在 Component 接口中實作子部件的相關操作,比如put() , putAll() 等
- Node 是組合模式中的葉子節點 leaf,裡面沒有關鍵方法的實作,是最末端的存放資料的結構
下節預告
OK,到這裡,組合模式的相關内容就結束了,下一節,我們開啟外觀模式的學習,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~