## 引入
在閻宏博士的《JAVA與模式》一書中開頭是這樣描述合成(Composite)模式(組合模式)的:
> 組合模式屬于對象的結構模式,有時又叫做“部分——整體”模式。組合模式將對象組織到樹結構中,可以用來描述整體與部分的關系。組合模式可以使客戶端將單純元素與復合元素同等看待。
## 定義
組合模式組合多個對象形成樹形結構以表示“整體-部分”的結構層次。
組合模式對單個對象(葉子對象)和組合對象(組合對象)具有一致性,它將對象組織到樹結構中,可以用來描述整體與部分的關系。同時它也模糊了簡單元素(葉子對象)和復雜元素(容器對象)的概念,使得客戶能夠像處理簡單元素一樣來處理復雜元素,從而使客戶程序能夠與復雜元素的內部結構解耦。
組合模式把部分和整體的關系用樹結構表示出來。組合模式使得客戶端把一個個單獨的成分對象和由它們復合而成的合成對象同等看待。
比如,一個文件系統就是一個典型的組合模式系統。下圖是常見的計算機XP文件系統的一部分。

從上圖可以看出,文件系統是一個樹結構,樹上長有節點。樹的節點有兩種,一種是樹枝節點,即目錄,有內部樹結構,在圖中涂有顏色;另一種是文件,即樹葉節點,沒有內部樹結構。
顯然,可以把目錄和文件當做同一種對象同等對待和處理,這也就是組合模式的應用。
組合模式可以不提供父對象的管理方法,但是組合模式必須在合適的地方提供子對象的管理方法,諸如:add()、remove()、以及getChild()等。在使用組合模式中需要注意一點也是組合模式最關鍵的地方:葉子對象和組合對象實現相同的接口。這就是組合模式能夠將葉子節點和對象節點進行一致處理的原因。
組合模式的實現根據所實現接口的區別分為兩種形式,分別稱為安全式和透明式。
## 結構和代碼實現
### 安全式組合模式
**結構**
安全模式的組合模式要求管理聚集的方法只出現在樹枝構件類中,而不出現在樹葉構件類中。

這種形式涉及到三個角色:
● 抽象構件(Component)角色:這是一個抽象角色,它給參加組合的對象定義出公共的接口及其默認行為,可以用來管理所有的子對象。合成對象通常把它所包含的子對象當做類型為Component的對象。在安全式的合成模式里,構件角色并不定義出管理子對象的方法,這一定義由樹枝構件對象給出。
● 樹葉構件(Leaf)角色:樹葉對象是沒有下級子對象的對象,定義出參加組合的原始對象的行為。
● 樹枝構件(Composite)角色:代表參加組合的有下級子對象的對象。樹枝構件類給出所有的管理子對象的方法,如add()、remove()以及getChild()。
**代碼實現**
抽象構件角色類
~~~
public interface Component {
/**
* 輸出組建自身的名稱
*/
public void printStruct(String preStr);
}
~~~
樹枝構件角色類
~~~
public class Composite implements Component {
/**
* 用來存儲組合對象中包含的子組件對象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對象的名字
*/
private String name;
/**
* 構造方法,傳入組合對象的名字
* @param name 組合對象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對象的自身結構
* @param preStr 前綴,主要是按照層級拼接空格,實現向后縮進
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那么就輸出這些子組件對象
if(this.childComponents != null){
//添加兩個空格,表示向后縮進兩個空格
preStr += " ";
//輸出當前對象的子對象
for(Component c : childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}
}
}
~~~
樹葉構件角色類
~~~
public class Leaf implements Component {
/**
* 葉子對象的名字
*/
private String name;
/**
* 構造方法,傳入葉子對象的名稱
* @param name 葉子對象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
* @param preStr 前綴,主要是按照層級拼接的空格,實現向后縮進
*/
@Override
public void printStruct(String preStr) {
// TODO Auto-generated method stub
System.out.println(preStr + "-" + name);
}
}
~~~
客戶端類
~~~
public class Client {
public static void main(String[]args){
Composite root = new Composite("服裝");
Composite c1 = new Composite("男裝");
Composite c2 = new Composite("女裝");
Leaf leaf1 = new Leaf("襯衫");
Leaf leaf2 = new Leaf("夾克");
Leaf leaf3 = new Leaf("裙子");
Leaf leaf4 = new Leaf("套裝");
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
root.printStruct("");
}
}
~~~
可以看出,樹枝構件類(Composite)給出了addChild()、removeChild()以及getChild()等方法的聲明和實現,而樹葉構件類則沒有給出這些方法的聲明或實現。這樣的做法是安全的做法,由于這個特點,客戶端應用程序不可能錯誤地調用樹葉構件的聚集方法,因為樹葉構件沒有這些方法,調用會導致編譯錯誤。
安全式組合模式的缺點是不夠透明,因為樹葉類和樹枝類將具有不同的接口。
### 透明式組合模式
**結構**
與安全式的組合模式不同的是,透明式的組合模式要求所有的具體構件類,不論樹枝構件還是樹葉構件,均符合一個固定接口。

**代碼實現**
抽象構件角色類
~~~
public abstract class Component {
/**
* 輸出組建自身的名稱
*/
public abstract void printStruct(String preStr);
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
/**
* 缺省實現,拋出異常,因為葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
/**
* 缺省實現,拋出異常,因為葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
/**
* 缺省實現,拋出異常,因為葉子對象沒有此功能
* 或者子組件沒有實現這個功能
*/
throw new UnsupportedOperationException("對象不支持此功能");
}
}
~~~
樹枝構件角色類,此類將implements Conponent改為extends Conponent,其他地方無變化。
~~~
public class Composite extends Component {
/**
* 用來存儲組合對象中包含的子組件對象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對象的名字
*/
private String name;
/**
* 構造方法,傳入組合對象的名字
* @param name 組合對象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個子構件對象
* @param child 子構件對象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法,刪除一個子構件對象
* @param index 子構件對象的下標
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構件對象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對象的自身結構
* @param preStr 前綴,主要是按照層級拼接空格,實現向后縮進
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那么就輸出這些子組件對象
if(this.childComponents != null){
//添加兩個空格,表示向后縮進兩個空格
preStr += " ";
//輸出當前對象的子對象
for(Component c : childComponents){
//遞歸輸出每個子對象
c.printStruct(preStr);
}
}
}
}
~~~
樹葉構件角色類,此類將implements Conponent改為extends Conponent,其他地方無變化。
~~~
public class Leaf extends Component {
/**
* 葉子對象的名字
*/
private String name;
/**
* 構造方法,傳入葉子對象的名稱
* @param name 葉子對象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對象的結構,葉子對象沒有子對象,也就是輸出葉子對象的名字
* @param preStr 前綴,主要是按照層級拼接的空格,實現向后縮進
*/
@Override
public void printStruct(String preStr) {
// TODO Auto-generated method stub
System.out.println(preStr + "-" + name);
}
}
~~~
客戶端類的主要變化是不再區分Composite對象和Leaf對象。
~~~
public class Client {
public static void main(String[]args){
Component root = new Composite("服裝");
Component c1 = new Composite("男裝");
Component c2 = new Composite("女裝");
Component leaf1 = new Leaf("襯衫");
Component leaf2 = new Leaf("夾克");
Component leaf3 = new Leaf("裙子");
Component leaf4 = new Leaf("套裝");
root.addChild(c1);
root.addChild(c2);
c1.addChild(leaf1);
c1.addChild(leaf2);
c2.addChild(leaf3);
c2.addChild(leaf4);
root.printStruct("");
}
}
~~~
可以看出,客戶端無需再區分操作的是樹枝對象(Composite)還是樹葉對象(Leaf)了;對于客戶端而言,操作的都是Component對象。
### 兩種實現方法的選擇
這里所說的安全性組合模式是指:從客戶端使用組合模式上看是否更安全,如果是安全的,那么就不會有發生誤操作的可能,能訪問的方法都是被支持的。
這里所說的透明性組合模式是指:從客戶端使用組合模式上,是否需要區分到底是“樹枝對象”還是“樹葉對象”。如果是透明的,那就不用區分,對于客戶而言,都是Compoent對象,具體的類型對于客戶端而言是透明的,是無須關心的。
對于組合模式而言,在安全性和透明性上,會更看重透明性,畢竟組合模式的目的是:讓客戶端不再區分操作的是樹枝對象還是樹葉對象,而是以一個統一的方式來操作。
而且對于安全性的實現,需要區分是樹枝對象還是樹葉對象。有時候,需要將對象進行類型轉換,卻發現類型信息丟失了,只好強行轉換,這種類型轉換必然是不夠安全的。
因此在使用組合模式的時候,建議**多采用透明性**的實現方式。
## 優點
* 1、可以清楚地定義分層次的復雜對象,表示對象的全部或部分層次,使得增加新構件也更容易。
* 2、客戶端調用簡單,客戶端可以一致的使用組合結構或其中單個對象。
* 3、定義了包含葉子對象和容器對象的類層次結構,葉子對象可以被組合成更復雜的容器對象,而這個容器對象又可以被組合,這樣不斷遞歸下去,可以形成復雜的樹形結構。
* 4、更容易在組合體內加入對象構件,客戶端不必因為加入了新的對象構件而更改原有代碼。
## 缺點
* 1、使設計變得更加抽象,對象的業務規則如果很復雜,則實現組合模式具有很大挑戰性,而且不是所有的方法都與葉子對象子類都有關聯。
## 適用場景
* 1、需要表示一個對象整體或部分層次,在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,可以一致地對待它們。
* 2、讓客戶能夠忽略不同對象層次的變化,客戶端可以針對抽象構件編程,無須關心對象層次結構的細節。
## 總結
* 1、 組合模式用于將多個對象組合成樹形結構以表示“整體-部分”的結構層次。組合模式對單個對象(葉子對象)和組合對象(容器對象)的使用具有一致性。
* 2、 組合對象的關鍵在于它定義了一個抽象構建類,它既可表示葉子對象,也可表示容器對象,客戶僅僅需要針對這個抽象構建進行編程,無須知道他是葉子對象還是容器對象,都是一致對待。
* 3、 組合模式雖然能夠非常好地處理層次結構,也使得客戶端程序變得簡單,但是它也使得設計變得更加抽象,而且也很難對容器中的構件類型進行限制,這會導致在增加新的構件時會產生一些問題。
- java
- 設計模式
- 設計模式總覽
- 設計原則
- 工廠方法模式
- 抽象工廠模式
- 單例模式
- 建造者模式
- 原型模式
- 適配器模式
- 裝飾者模式
- 代理模式
- 外觀模式
- 橋接模式
- 組合模式
- 享元模式
- 策略模式
- 模板方法模式
- 觀察者模式
- 迭代子模式
- 責任鏈模式
- 命令模式
- 備忘錄模式
- 狀態模式
- 訪問者模式
- 中介者模式
- 解釋器模式
- 附錄
- JVM相關
- JVM內存結構
- Java虛擬機的內存組成以及堆內存介紹
- Java堆和棧
- 附錄-數據結構的堆棧和內存分配的堆區棧區的區別
- Java內存之Java 堆
- Java內存之虛擬機和內存區域概述
- Java 內存之方法區和運行時常量池
- Java 內存之直接內存(堆外內存)
- JAVA內存模型
- Java內存模型介紹
- 內存模型如何解決緩存一致性問題
- 深入理解Java內存模型——基礎
- 深入理解Java內存模型——重排序
- 深入理解Java內存模型——順序一致性
- 深入理解Java內存模型——volatile
- 深入理解Java內存模型——鎖
- 深入理解Java內存模型——final
- 深入理解Java內存模型——總結
- 內存可見性
- JAVA對象模型
- JVM內存結構 VS Java內存模型 VS Java對象模型
- Java的對象模型
- Java的對象頭
- HotSpot虛擬機
- HotSpot虛擬機對象探秘
- 深入分析Java的編譯原理
- Java虛擬機的鎖優化技術
- 對象和數組并不是都在堆上分配內存的
- 垃圾回收
- JVM內存管理及垃圾回收
- JVM 垃圾回收器工作原理及使用實例介紹
- JVM內存回收理論與實現(對象存活的判定)
- JVM參數及調優
- CMS GC日志分析
- JVM實用參數(一)JVM類型以及編譯器模式
- JVM實用參數(二)參數分類和即時(JIT)編譯器診斷
- JVM實用參數(三)打印所有XX參數及值
- JVM實用參數(四)內存調優
- JVM實用參數(五)新生代垃圾回收
- JVM實用參數(六) 吞吐量收集器
- JVM實用參數(七)CMS收集器
- JVM實用參數(八)GC日志
- Java性能調優原則
- JVM 優化經驗總結
- 面試題整理
- 面試題1
- java日志規約
- Spring安全
- OAtuth2.0簡介
- Spring Session 簡介(一)
- Spring Session 簡介(二)
- Spring Session 簡介(三)
- Spring Security 簡介(一)
- Spring Security 簡介(二)
- Spring Security 簡介(三)
- Spring Security 簡介(四)
- Spring Security 簡介(五)
- Spring Security Oauth2 (一)
- Spring Security Oauth2 (二)
- Spring Security Oauth2 (三)
- SpringBoot
- Shiro
- Shiro和Spring Security對比
- Shiro簡介
- Session、Cookie和Cache
- Web Socket
- Spring WebFlux