Java設(shè)計模式之組合模式
本文通過老王和小王探討書房、書架、各類書的管理問題,引出結(jié)構(gòu)型設(shè)計模式家族中的一個重要成員——組合模式,本文會給予兩種組合模式的典型代碼實現(xiàn),為了加深理解會在第三部分應(yīng)用中介紹組合模式在源碼中的實際運用,最后總結(jié)該設(shè)計模式學(xué)習(xí)后的一些思考。
讀者可以拉取完整代碼到本地進行學(xué)習(xí),實現(xiàn)代碼均測試通過后上傳到碼云,本地源碼下載。
一、引出問題
上篇文章中老王給小王買車以后,小王對老王感激涕零,看著老王凌亂的書房,小王提出要幫助老王整理整理他的書架。
小王開始了他的分析。老王平時博覽群書,中文、英文、梵文...每個語種占滿了書架,而每個語種中又分經(jīng)濟學(xué)、計算機學(xué)、社會學(xué)等等類目。這是典型的分層次結(jié)構(gòu),將語種比作是圖書的子類,類目是語種的子類結(jié)構(gòu)劃分。
將圖書、語種、類目都看做是組織結(jié)構(gòu),他們之間沒有繼承關(guān)系,而是一個樹形結(jié)構(gòu),可以更好的實現(xiàn)管理操作。
二、概念與使用
實際上,小王提出來的設(shè)計思路正是結(jié)構(gòu)型設(shè)計模式中的組合模式,我們首先看一下組合模式的相關(guān)概念,組合模式(Composite Pattern),又叫部分整體模式,它創(chuàng)建了對象組的樹形結(jié)構(gòu),將對象組合成樹狀結(jié)構(gòu)以表示“整體-部分”的層次關(guān)系。組合模式依據(jù)樹形結(jié)構(gòu)來組合對象,用來表示部分以及整體層次。
組合模式使得用戶對單個對象和組合對象的訪問具有一致性,即:組合能讓客戶以一致的方式處理個別對象以及組合對象。
用大白話解釋也就是,在實際應(yīng)用中將所有圖書依照樹形模式進行組合,老王尋找書籍時,無論是訪問某一類書還是某一個語種的書,使用同樣的姿勢即可,保證了訪問的一致性。
在該模式中應(yīng)該是有三個角色:
1、Root :這是組合中對象聲明接口,在適當(dāng)情況下,實現(xiàn)所有類共有的接口默認行為,用于訪問和管理Root 子部件, Root 可以是抽象類或者接口。
2、Branches:非葉子節(jié)點用于存儲子部件,在Root接口中實現(xiàn)了 子部件的相關(guān)操作。
2、Leaf : 在組合中表示葉子節(jié)點,葉子節(jié)點沒有子節(jié)點。
小王分析的頭頭是道,老王提出來了他的疑問。
當(dāng)我按語種查找還是按類目查找是使用的方法有時候是不一樣的,如果你把所有方法都定義在Root中,在語種或者類目中實現(xiàn)中是無意義的,而且這違背了接口隔離原則。
小王覺得說的對也不對,如果我改成不在Root中定義,那么我在客戶端調(diào)用的時候就需要判斷是枝還是葉了,增加了繁雜的邏輯判斷,而且相比另外一種變得不透明了,依賴倒置原則也沒有遵守。
兩種方式似乎都有缺陷,小王陷入了糾結(jié)不知道該如何取舍,老王提出了他的一些見解,沒有任何一個設(shè)計模式是完全沒有缺點的,兩種都有各自的好處,在實際的運用中根據(jù)條件進行取舍,而正確選擇的前提就是要對所有的設(shè)計模式充分的把握。
上面兩種就對應(yīng)組合模式中的兩個大分類、①透明組合模式、安全組合模式。
①透明組合模式把所有的公共方法都定義在Root中,這樣做的好處就是客戶端無需分辨是葉子節(jié)點(Leaf)和樹枝節(jié)點(Branches),他們具備完全一致的接口;缺點是葉子節(jié)點(Leaf)會繼承得到一些它所不需要(管理子類操作的方法)的方法,這與設(shè)計模式接口隔離原則相違背。
②安全組合模式的好處是接口定義職責(zé)清晰,符合設(shè)計模式單一職責(zé)原則和接口隔離原則;缺點是客戶需要區(qū)分樹枝節(jié)點(Branches)和葉子節(jié)點(Leaf),這樣才能正確處理各個層次的操作,客戶端依賴抽象(Root),違背了依賴倒置原則。
我們把兩種的方式實現(xiàn),讀者對比他們之間的區(qū)別。
安全模式
Root(根節(jié)點):
/** * @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(樹枝節(jié)點)
/** * @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(葉子節(jié)點)
/** * @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("安全組合模式..."); // 來一個根節(jié)點 BranchesLanguages BranchesRoot = new BranchesLanguages("root/書"); // 來一個樹枝節(jié)點 BranchesLanguages branchA = new BranchesLanguages("------branchA/英語"); BranchesLanguages branchB = new BranchesLanguages("------branchB/中文"); // 來一個葉子節(jié)點 RootBook leafA = new LeafClassify("------leafA/經(jīng)濟學(xué)"); RootBook leafB = new LeafClassify("------leafB/計算機學(xué)"); RootBook leafC = new LeafClassify("------leafC/法學(xué)"); BranchesRoot.addChild(branchA); BranchesRoot.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = BranchesRoot.operation(); System.out.println(result); } }
透明模式
Root(根節(jié)點):
/** * @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(樹枝節(jié)點)
/** * @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(葉子節(jié)點)
/** * @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("透明組合模式..."); // 來一個根節(jié)點 RootBook BranchesRoot = new BranchesLanguages("root/書"); // 來一個樹枝節(jié)點 RootBook branchA = new BranchesLanguages("------branchA/英語"); RootBook branchB = new BranchesLanguages("------branchB/漢語"); // 來一個葉子節(jié)點 RootBook leafA = new LeafClassify("------leafA/計算機學(xué)"); RootBook leafB = new LeafClassify("------leafB/法學(xué)"); RootBook leafC = new LeafClassify("------leafC/社會學(xué)"); BranchesRoot.addChild(branchA); BranchesRoot.addChild(leafC); branchA.addChild(leafA); branchA.addChild(branchB); branchB.addChild(leafB); String result = BranchesRoot.operation(); System.out.println(result); } }
使用組合模式的兩種實現(xiàn)方法,這樣就對老王的書架改造工程就完成了,對憑空捏造出來的需求有些讀者看完想必還是云里霧里。我們結(jié)合JDK的源碼和一些開發(fā)常用框架,再次深入源碼對組合模式的使用。
三、應(yīng)用
通過查詢資料可知,組合模式在Jdk中的應(yīng)用主要是集合類HashMap和Mybtis中的SqlNode。
我們分別看其實現(xiàn)。
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 = e.getValue(); putVal(hash(key), key, value, false, evict); } } } ... }
putAll()方法傳入的是Map對象,Map就是一個抽象構(gòu)件(同時這個構(gòu)件中只支持健值對的存儲格式),而HashMap是一個中間構(gòu)件,HashMap中的Node節(jié)點就是葉子節(jié)點。
Node是HashMap中的一個內(nèi)部類,HashMap的存儲節(jié)點指的正是Node,讀者可以重點看這個類的實現(xiàn)。
在這個實例中,HashMap就是樹枝節(jié)點,Node就是葉節(jié)點,Map就是根節(jié)點。
2、Mybtis中的SqlNode
SqlNode是一個接口,主要功能就是構(gòu)造SQL語句。
public interface SqlNode { boolean apply(DynamicContext context); }
SqlNode有一大堆的實現(xiàn)類,我們看其中的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就充當(dāng)組合模式中的Root,而他的眾多子類作用就在于拼接各種類型的SQL,在組合模式的角色中相當(dāng)于樹枝節(jié)點。其中在TrimSqlNode 中有一個子類WhereSqlNode就充當(dāng)組合模式中的樹葉節(jié)點。
這兩個都屬于組合模式中的典型例子,讀者體會下使用這種模式的好處,和如果不使用組合模式應(yīng)該怎樣實現(xiàn)。
通過這兩個例子我們應(yīng)該可以看到,設(shè)計模式的使用中并不是完全遵循各自的角色,更多的是設(shè)計模式中的一些變種,讀者不深入源碼并不能了解到該模式的實現(xiàn)細節(jié)。讀者需要做的就是盡可能的熟悉設(shè)計模式,在自己開發(fā)過程中可以“擇優(yōu)錄取”。
四、總結(jié)
到這里組合模式也就介紹完了,這種模式的優(yōu)缺點都非常的明顯,優(yōu)點就在于清楚的定義分層次的結(jié)構(gòu),在調(diào)用時忽略他們之間的差異,方便對整個層次進行控制,但是組合模式會違反依賴倒置原則。
理解是一回事,在實際應(yīng)用中能正確的使用它就是另外一回事了。
讀者要對每種設(shè)計模式都能做到心中有數(shù),當(dāng)我們在實際編程中,在潛意識里有各個設(shè)計模式的大體輪廓,參考代入進各種設(shè)計模式中,對于簡化開發(fā)和易于維護性有沒有好的幫助,選擇一個最優(yōu)的設(shè)計模式。
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,謝謝大家對腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請查看下面相關(guān)鏈接
相關(guān)文章
詳解SpringBoot應(yīng)用服務(wù)啟動與安全終止
這篇文章主要介紹了SpringBoot應(yīng)用服務(wù)啟動與安全終止,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04詳解如何獨立使用ribbon實現(xiàn)業(yè)務(wù)客戶端負載均衡
這篇文章主要為大家介紹了詳解如何獨立使用ribbon實現(xiàn)業(yè)務(wù)客戶端負載均衡,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-06-06zookeeper+Springboot實現(xiàn)服務(wù)器動態(tài)上下線監(jiān)聽教程詳解
這篇文章主要介紹了zookeeper+Springboot實現(xiàn)服務(wù)器動態(tài)上下線監(jiān)聽,主要介紹了什么是服務(wù)器動態(tài)上下線監(jiān)聽及為什么要實現(xiàn)對服務(wù)器上下線的監(jiān)聽,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2022-06-06Java中執(zhí)行docker命令的實現(xiàn)示例
本文主要介紹了Java中執(zhí)行docker命令的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究
這篇文章主要為大家介紹了HttpClient的DnsResolver自定義DNS解析另一種選擇深入研究,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-10-10java數(shù)組及arrays類對數(shù)組的操作實例
下面小編就為大家?guī)硪黄猨ava數(shù)組及arrays類對數(shù)組的操作實例。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-10-10