本文通過老王和小王探討書房、書架、各類書的管理問題,引出結構型設計模式家族中的一個重要成員——組合模式,本文會給予兩種組合模式的典型代碼實作,為了加深理解會在第三部分應用中介紹組合模式在原始碼中的實際運用,最后總結該設計模式學習后的一些思考,
讀者可以拉取完整代碼到本地進行學習,實作代碼均測驗通過后上傳到碼云,
一、引出問題
上篇文章中老王給小王買車以后,小王對老王感激涕零,看著老王凌亂的書房,小王提出要幫助老王整理整理他的書架,
小王開始了他的分析,老王平時博覽群書,中文、英文、梵文...每個語種占滿了書架,而每個語種中又分經濟學、計算機學、社會學等等類目,這是典型的分層次結構,將語種比作是圖書的子類,類目是語種的子類結構劃分,
將圖書、語種、類目都看做是組織結構,他們之間沒有繼承關系,而是一個樹形結構,可以更好的實作管理操作,
二、概念與使用
實際上,小王提出來的設計思路正是結構型設計模式中的組合模式,我們首先看一下組合模式的相關概念,組合模式(Composite Pattern),又叫部分整體模式,它創建了物件組的樹形結構,將物件組合成樹狀結構以表示“整體-部分”的層次關系,組合模式依據樹形結構來組合物件,用來表示部分以及整體層次,
組合模式使得用戶對單個物件和組合物件的訪問具有一致性,即:組合能讓客戶以一致的方式處理個別物件以及組合物件,
用大白話解釋也就是,在實際應用中將所有圖書依照樹形模式進行組合,老王尋找書籍時,無論是訪問某一類書還是某一個語種的書,使用同樣的姿勢即可,保證了訪問的一致性,
在該模式中應該是有三個角色:
1、Root :這是組合中物件宣告介面,在適當情況下,實作所有類共有的介面默認行為,用于訪問和管理Root 子部件, Root 可以是抽象類或者介面,
2、Branches:非葉子節點用于存盤子部件,在Root介面中實作了 子部件的相關操作,
2、Leaf : 在組合中表示葉子節點,葉子節點沒有子節點,
小王分析的頭頭是道,老王提出來了他的疑問,
當我按語種查找還是按類目查找是使用的方法有時候是不一樣的,如果你把所有方法都定義在Root中,在語種或者類目中實作中是無意義的,而且這違背了介面隔離原則,
小王覺得說的對也不對,如果我改成不在Root中定義,那么我在客戶端呼叫的時候就需要判斷是枝還是葉了,增加了繁雜的邏輯判斷,而且相比另外一種變得不透明了,依賴倒置原則也沒有遵守,
兩種方式似乎都有缺陷,小王陷入了糾結不知道該如何取舍,老王提出了他的一些見解,沒有任何一個設計模式是完全沒有缺點的,兩種都有各自的好處,在實際的運用中根據條件進行取舍,而正確選擇的前提就是要對所有的設計模式充分的把握,
上面兩種就對應組合模式中的兩個大分類、①透明組合模式、安全組合模式,
①透明組合模式把所有的公共方法都定義在Root中,這樣做的好處就是客戶端無需分辨是葉子節點(Leaf)和樹枝節點(Branches),他們具備完全一致的介面;缺點是葉子節點(Leaf)會繼承得到一些它所不需要(管理子類操作的方法)的方法,這與設計模式介面隔離原則相違背,
②安全組合模式的好處是介面定義職責清晰,符合設計模式單一職責原則和介面隔離原則;缺點是客戶需要區分樹枝節點(Branches)和葉子節點(Leaf),這樣才能正確處理各個層次的操作,客戶端依賴抽象(Root),違背了依賴倒置原則,
我們把兩種的方式實作,讀者對比他們之間的區別,
安全模式
Root(根節點):
/**
* @author tcy
* @Date 08-08-2022
*/
public abstract class RootBook {
protected String name;
public RootBook(String name) {
this.name = name;
}
public abstract String operation();
}
Branches(樹枝節點)
/**
* @author tcy
* @Date 08-08-2022
*/
public class BranchesLanguages extends RootBook {
private List<RootBook> roots;
public BranchesLanguages(String name) {
super(name);
this.roots = new ArrayList<RootBook>();
}
public String operation() {
StringBuilder builder = new StringBuilder(this.name);
for (RootBook component : this.roots) {
builder.append("\n");
builder.append(component.operation());
}
return builder.toString();
}
public boolean addChild(RootBook component) {
return this.roots.add(component);
}
public boolean removeChild(RootBook component) {
return this.roots.remove(component);
}
public RootBook getChild(int index) {
return this.roots.get(index);
}
}
Leaf(葉子節點)
/**
* @author tcy
* @Date 08-08-2022
*/
public class LeafClassify extends RootBook {
public LeafClassify(String name) {
super(name);
}
@Override
public String operation() {
return this.name;
}
}
客戶端:
/**
* @author tcy
* @Date 08-08-2022
*/
public class Client {
public static void main(String[] args) {
System.out.println("安全組合模式...");
// 來一個根節點
BranchesLanguages BranchesRoot = new BranchesLanguages("root/書");
// 來一個樹枝節點
BranchesLanguages branchA = new BranchesLanguages("------branchA/英語");
BranchesLanguages branchB = new BranchesLanguages("------branchB/中文");
// 來一個葉子節點
RootBook leafA = new LeafClassify("------leafA/經濟學");
RootBook leafB = new LeafClassify("------leafB/計算機學");
RootBook leafC = new LeafClassify("------leafC/法學");
BranchesRoot.addChild(branchA);
BranchesRoot.addChild(leafC);
branchA.addChild(leafA);
branchA.addChild(branchB);
branchB.addChild(leafB);
String result = BranchesRoot.operation();
System.out.println(result);
}
}
透明模式
Root(根節點):
/**
* @author tcy
* @Date 08-08-2022
*/
public abstract class RootBook {
protected String name;
public RootBook(String name) {
this.name = name;
}
public abstract String operation();
public boolean addChild(RootBook component) {
throw new UnsupportedOperationException("addChild not supported!");
}
public boolean removeChild(RootBook component) {
throw new UnsupportedOperationException("removeChild not supported!");
}
public RootBook getChild(int index) {
throw new UnsupportedOperationException("getChild not supported!");
}
}
Branches(樹枝節點)
/**
* @author tcy
* @Date 08-08-2022
*/
public class BranchesLanguages extends RootBook {
private List<RootBook> roots;
public BranchesLanguages(String name) {
super(name);
this.roots = new ArrayList<RootBook>();
}
public String operation() {
StringBuilder builder = new StringBuilder(this.name);
for (RootBook component : this.roots) {
builder.append("\n");
builder.append(component.operation());
}
return builder.toString();
}
@Override
public boolean addChild(RootBook component) {
return this.roots.add(component);
}
@Override
public boolean removeChild(RootBook component) {
return this.roots.remove(component);
}
@Override
public RootBook getChild(int index) {
return this.roots.get(index);
}
}
Leaf(葉子節點)
/**
* @author tcy
* @Date 08-08-2022
*/
public class LeafClassify extends RootBook {
public LeafClassify(String name) {
super(name);
}
@Override
public String operation() {
return this.name;
}
}
客戶端:
/**
* @author tcy
* @Date 08-08-2022
*/
public class Client {
public static void main(String[] args) {
System.out.println("透明組合模式...");
// 來一個根節點
RootBook BranchesRoot = new BranchesLanguages("root/書");
// 來一個樹枝節點
RootBook branchA = new BranchesLanguages("------branchA/英語");
RootBook branchB = new BranchesLanguages("------branchB/漢語");
// 來一個葉子節點
RootBook leafA = new LeafClassify("------leafA/計算機學");
RootBook leafB = new LeafClassify("------leafB/法學");
RootBook leafC = new LeafClassify("------leafC/社會學");
BranchesRoot.addChild(branchA);
BranchesRoot.addChild(leafC);
branchA.addChild(leafA);
branchA.addChild(branchB);
branchB.addChild(leafB);
String result = BranchesRoot.operation();
System.out.println(result);
}
}
使用組合模式的兩種實作方法,這樣就對老王的書架改造工程就完成了,對憑空捏造出來的需求有些讀者看完想必還是云里霧里,我們結合JDK的原始碼和一些開發常用框架,再次深入原始碼對組合模式的使用,
三、應用
通過查詢資料可知,組合模式在Jdk中的應用主要是集合類HashMap和Mybtis中的SqlNode,
我們分別看其實作,
1、jdk中HashMap的運用
在HashMap中有一個父類AbstractMap和一個子類Node,如下圖

我們看下源代碼:
public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {
...
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
...
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
int s = m.size();
if (s > 0) {
if (table == null) { // pre-size
float ft = ((float)s / loadFactor) + 1.0F;
int t = ((ft < (float)MAXIMUM_CAPACITY) ?
(int)ft : MAXIMUM_CAPACITY);
if (t > threshold)
threshold = tableSizeFor(t);
}
else if (s > threshold)
resize();
for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
K key = e.getKey();
V value = https://www.cnblogs.com/tianClassmate/p/e.getValue();
putVal(hash(key), key, value, false, evict);
}
}
}
...
}
putAll()方法傳入的是Map物件,Map就是一個抽象構件(同時這個構件中只支持健值對的存盤格式),而HashMap是一個中間構件,HashMap中的Node節點就是葉子節點,
Node是HashMap中的一個內部類,HashMap的存盤節點指的正是Node,讀者可以重點看這個類的實作,
在這個實體中,HashMap就是樹枝節點,Node就是葉節點,Map就是根節點,
2、Mybtis中的SqlNode
SqlNode是一個介面,主要功能就是構造SQL陳述句,
public interface SqlNode {
boolean apply(DynamicContext context);
}

SqlNode有一大堆的實作類,我們看其中的MixedSqlNode,
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
SqlNode就充當組合模式中的Root,而他的眾多子類作用就在于拼接各種型別的SQL,在組合模式的角色中相當于樹枝節點,其中在TrimSqlNode 中有一個子類WhereSqlNode就充當組合模式中的樹葉節點,
這兩個都屬于組合模式中的典型例子,讀者體會下使用這種模式的好處,和如果不使用組合模式應該怎樣實作,
通過這兩個例子我們應該可以看到,設計模式的使用中并不是完全遵循各自的角色,更多的是設計模式中的一些變種,讀者不深入原始碼并不能了解到該模式的實作細節,讀者需要做的就是盡可能的熟悉設計模式,在自己開發程序中可以“擇優錄取”,
四、總結
到這里組合模式也就介紹完了,這種模式的優缺點都非常的明顯,優點就在于清楚的定義分層次的結構,在呼叫時忽略他們之間的差異,方便對整個層次進行控制,但是組合模式會違反依賴倒置原則,
理解是一回事,在實際應用中能正確的使用它就是另外一回事了,
讀者要對每種設計模式都能做到心中有數,當我們在實際編程中,在潛意識里有各個設計模式的大體輪廓,參考代入進各種設計模式中,對于簡化開發和易于維護性有沒有好的幫助,選擇一個最優的設計模式,
推薦讀者,參考軟體設計七大原則 認真閱讀往期的文章,認真體會,
創建型設計模式:
一、設計模式之工廠方法和抽象工廠
二、設計模式之單例和原型
三、設計模式之建造者模式
結構型設計模式:
四、設計模式之代理模式
五、設計模式之配接器模式
六、橋接模式
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/501401.html
標籤:設計模式
上一篇:怎么看待 Soul 聚焦 Z 世代社交,一腳跨入元宇宙?
下一篇:設計模式之組合模式
