天天看點

寂然解讀設計模式 - 組合模式

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,到這裡,組合模式的相關内容就結束了,下一節,我們開啟外觀模式的學習,最後,希望大家在學習的過程中,能夠感覺到設計模式的有趣之處,高效而愉快的學習,那我們下期見~