1 介紹
組合模式屬于對(duì)象的結(jié)構(gòu)模式返吻,有時(shí)又叫做“部分——整體”模式暇番。
1.1 什么是組合模式
將對(duì)象組合成樹形結(jié)構(gòu)以表示“部分-整體”的層次結(jié)構(gòu),使得用戶對(duì)單個(gè)對(duì)象和組合對(duì)象的使用具有一致性思喊。
1.2 解決了什么問題
它在我們樹型結(jié)構(gòu)的問題中,模糊了簡(jiǎn)單元素和復(fù)雜元素的概念次酌,客戶程序可以向處理簡(jiǎn)單元素一樣來處理復(fù)雜元素恨课,從而使得客戶程序與復(fù)雜元素的內(nèi)部結(jié)構(gòu)解耦。
2 原理
合成模式的實(shí)現(xiàn)根據(jù)所實(shí)現(xiàn)接口的區(qū)別分為兩種形式岳服,分別稱為安全式和透明式剂公。
2.1 安全式合成模式的結(jié)構(gòu)
涉及到三個(gè)角色:
- Component:抽象構(gòu)件角色。這是一個(gè)抽象角色吊宋,它給參加組合的對(duì)象定義出公共的接口及其默認(rèn)行為纲辽,可以用來管理所有的子對(duì)象。合成對(duì)象通常把它所包含的子對(duì)象當(dāng)做類型為Component的對(duì)象璃搜。在安全式的合成模式里拖吼,構(gòu)件角色并不定義出管理子對(duì)象的方法,這一定義由樹枝構(gòu)件對(duì)象給出这吻。
- Leaf:樹葉構(gòu)件角色吊档。樹葉對(duì)象是沒有下級(jí)子對(duì)象的對(duì)象,定義出參加組合的原始對(duì)象的行為唾糯。
- Composite:樹枝構(gòu)件角色怠硼。代表參加組合的有下級(jí)子對(duì)象的對(duì)象。樹枝構(gòu)件類給出所有的管理子對(duì)象的方法移怯,如add()香璃、remove()以及getChild()。
2.1.1 uml圖
安全模式的合成模式要求管理聚集的方法只出現(xiàn)在樹枝構(gòu)件類中舟误,而不出現(xiàn)在樹葉構(gòu)件類中葡秒。
2.1.2 代碼示例
Component代碼示例
public interface Component {
/**
* 輸出組件自身的名稱
*/
public void printStruct(String preStr);
}
Leaf代碼示例
public class Leaf implements Component {
/**
* 葉子對(duì)象的名字
*/
private String name;
/**
* 構(gòu)造方法,傳入葉子對(duì)象的名稱
* @param name 葉子對(duì)象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對(duì)象的結(jié)構(gòu)脐帝,葉子對(duì)象沒有子對(duì)象同云,也就是輸出葉子對(duì)象的名字
* @param preStr 前綴,主要是按照層級(jí)拼接的空格堵腹,實(shí)現(xiàn)向后縮進(jìn)
*/
@Override
public void printStruct(String preStr) {
// TODO Auto-generated method stub
System.out.println(preStr + "-" + name);
}
}
Composite代碼示例
public class Composite implements Component {
/**
* 用來存儲(chǔ)組合對(duì)象中包含的子組件對(duì)象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對(duì)象的名字
*/
private String name;
/**
* 構(gòu)造方法炸站,傳入組合對(duì)象的名字
* @param name 組合對(duì)象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法,增加一個(gè)子構(gòu)件對(duì)象
* @param child 子構(gòu)件對(duì)象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法疚顷,刪除一個(gè)子構(gòu)件對(duì)象
* @param index 子構(gòu)件對(duì)象的下標(biāo)
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法旱易,返回所有子構(gòu)件對(duì)象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對(duì)象的自身結(jié)構(gòu)
* @param preStr 前綴禁偎,主要是按照層級(jí)拼接空格,實(shí)現(xiàn)向后縮進(jìn)
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件阀坏,那么就輸出這些子組件對(duì)象
if(this.childComponents != null){
//添加兩個(gè)空格如暖,表示向后縮進(jìn)兩個(gè)空格
preStr += " ";
//輸出當(dāng)前對(duì)象的子對(duì)象
for(Component c : childComponents){
//遞歸輸出每個(gè)子對(duì)象
c.printStruct(preStr);
}
}
}
}
調(diào)用示例
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("");
}
}
運(yùn)行結(jié)果
2.1.3 優(yōu)缺點(diǎn)
優(yōu)點(diǎn):可以看出,樹枝構(gòu)件類(Composite)給出了addChild()忌堂、removeChild()以及getChild()等方法的聲明和實(shí)現(xiàn)盒至,而樹葉構(gòu)件類則沒有給出這些方法的聲明或?qū)崿F(xiàn)。這樣的做法是安全的做法士修,由于這個(gè)特點(diǎn)枷遂,客戶端應(yīng)用程序不可能錯(cuò)誤地調(diào)用樹葉構(gòu)件的聚集方法,因?yàn)闃淙~構(gòu)件沒有這些方法棋嘲,調(diào)用會(huì)導(dǎo)致編譯錯(cuò)誤酒唉。
安全式合成模式的缺點(diǎn)是不夠透明,因?yàn)闃淙~類和樹枝類將具有不同的接口沸移。
2.2 透明式合成模式的結(jié)構(gòu)
與安全式的合成模式不同的是痪伦,透明式的合成模式要求所有的具體構(gòu)件類,不論樹枝構(gòu)件還是樹葉構(gòu)件雹锣,均符合一個(gè)固定接口网沾。
2.2.1 uml圖
2.2.2 代碼示例
Component代碼示例
public abstract class Component {
/**
* 輸出組建自身的名稱
*/
public abstract void printStruct(String preStr);
/**
* 聚集管理方法,增加一個(gè)子構(gòu)件對(duì)象
* @param child 子構(gòu)件對(duì)象
*/
public void addChild(Component child){
/**
* 缺省實(shí)現(xiàn)笆制,拋出異常绅这,因?yàn)槿~子對(duì)象沒有此功能
* 或者子組件沒有實(shí)現(xiàn)這個(gè)功能
*/
throw new UnsupportedOperationException("對(duì)象不支持此功能");
}
/**
* 聚集管理方法,刪除一個(gè)子構(gòu)件對(duì)象
* @param index 子構(gòu)件對(duì)象的下標(biāo)
*/
public void removeChild(int index){
/**
* 缺省實(shí)現(xiàn)在辆,拋出異常证薇,因?yàn)槿~子對(duì)象沒有此功能
* 或者子組件沒有實(shí)現(xiàn)這個(gè)功能
*/
throw new UnsupportedOperationException("對(duì)象不支持此功能");
}
/**
* 聚集管理方法,返回所有子構(gòu)件對(duì)象
*/
public List<Component> getChild(){
/**
* 缺省實(shí)現(xiàn)匆篓,拋出異常浑度,因?yàn)槿~子對(duì)象沒有此功能
* 或者子組件沒有實(shí)現(xiàn)這個(gè)功能
*/
throw new UnsupportedOperationException("對(duì)象不支持此功能");
}
}
Composite代碼示例
public class Composite extends Component {
/**
* 用來存儲(chǔ)組合對(duì)象中包含的子組件對(duì)象
*/
private List<Component> childComponents = new ArrayList<Component>();
/**
* 組合對(duì)象的名字
*/
private String name;
/**
* 構(gòu)造方法,傳入組合對(duì)象的名字
* @param name 組合對(duì)象的名字
*/
public Composite(String name){
this.name = name;
}
/**
* 聚集管理方法鸦概,增加一個(gè)子構(gòu)件對(duì)象
* @param child 子構(gòu)件對(duì)象
*/
public void addChild(Component child){
childComponents.add(child);
}
/**
* 聚集管理方法箩张,刪除一個(gè)子構(gòu)件對(duì)象
* @param index 子構(gòu)件對(duì)象的下標(biāo)
*/
public void removeChild(int index){
childComponents.remove(index);
}
/**
* 聚集管理方法,返回所有子構(gòu)件對(duì)象
*/
public List<Component> getChild(){
return childComponents;
}
/**
* 輸出對(duì)象的自身結(jié)構(gòu)
* @param preStr 前綴窗市,主要是按照層級(jí)拼接空格先慷,實(shí)現(xiàn)向后縮進(jìn)
*/
@Override
public void printStruct(String preStr) {
// 先把自己輸出
System.out.println(preStr + "+" + this.name);
//如果還包含有子組件,那么就輸出這些子組件對(duì)象
if(this.childComponents != null){
//添加兩個(gè)空格咨察,表示向后縮進(jìn)兩個(gè)空格
preStr += " ";
//輸出當(dāng)前對(duì)象的子對(duì)象
for(Component c : childComponents){
//遞歸輸出每個(gè)子對(duì)象
c.printStruct(preStr);
}
}
}
}
Leaf代碼示例
public class Leaf extends Component {
/**
* 葉子對(duì)象的名字
*/
private String name;
/**
* 構(gòu)造方法论熙,傳入葉子對(duì)象的名稱
* @param name 葉子對(duì)象的名字
*/
public Leaf(String name){
this.name = name;
}
/**
* 輸出葉子對(duì)象的結(jié)構(gòu),葉子對(duì)象沒有子對(duì)象摄狱,也就是輸出葉子對(duì)象的名字
* @param preStr 前綴脓诡,主要是按照層級(jí)拼接的空格无午,實(shí)現(xiàn)向后縮進(jìn)
*/
@Override
public void printStruct(String preStr) {
// TODO Auto-generated method stub
System.out.println(preStr + "-" + name);
}
}
調(diào)用示例
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("");
}
}
運(yùn)行結(jié)果
2.2.3 優(yōu)缺點(diǎn)
- 優(yōu)點(diǎn):可以看出,客戶端無需再區(qū)分操作的是樹枝對(duì)象(Composite)還是樹葉對(duì)象(Leaf)了祝谚;對(duì)于客戶端而言宪迟,操作的都是Component對(duì)象。
2.3 兩種實(shí)現(xiàn)方法的選擇
這里所說的安全性合成模式是指:從客戶端使用合成模式上看是否更安全交惯,如果是安全的次泽,那么就不會(huì)有發(fā)生誤操作的可能,能訪問的方法都是被支持的席爽。
這里所說的透明性合成模式是指:從客戶端使用合成模式上箕憾,是否需要區(qū)分到底是“樹枝對(duì)象”還是“樹葉對(duì)象”。如果是透明的拳昌,那就不用區(qū)分,對(duì)于客戶而言钠龙,都是Compoent對(duì)象炬藤,具體的類型對(duì)于客戶端而言是透明的,是無須關(guān)心的碴里。
對(duì)于合成模式而言沈矿,在安全性和透明性上,會(huì)更看重透明性咬腋,畢竟合成模式的目的是:讓客戶端不再區(qū)分操作的是樹枝對(duì)象還是樹葉對(duì)象羹膳,而是以一個(gè)統(tǒng)一的方式來操作。
而且對(duì)于安全性的實(shí)現(xiàn)根竿,需要區(qū)分是樹枝對(duì)象還是樹葉對(duì)象陵像。有時(shí)候,需要將對(duì)象進(jìn)行類型轉(zhuǎn)換寇壳,卻發(fā)現(xiàn)類型信息丟失了醒颖,只好強(qiáng)行轉(zhuǎn)換,這種類型轉(zhuǎn)換必然是不夠安全的壳炎。
因此在使用合成模式的時(shí)候泞歉,建議多采用透明性的實(shí)現(xiàn)方式。
2.4 優(yōu)缺點(diǎn)
-
組合模式的主要優(yōu)點(diǎn)如下:
- 組合模式可以清楚地定義分層次的復(fù)雜對(duì)象匿辩,表示對(duì)象的全部或部分層次腰耙,它讓客戶端忽略了層次的差異,方便對(duì)整個(gè)層次結(jié)構(gòu)進(jìn)行控制铲球。
- 客戶端可以一致地使用一個(gè)組合結(jié)構(gòu)或其中單個(gè)對(duì)象挺庞,不必關(guān)心處理的是單個(gè)對(duì)象還是整個(gè)組合結(jié)構(gòu),簡(jiǎn)化了客戶端代碼睬辐。
- 在組合模式中增加新的容器構(gòu)件和葉子構(gòu)件都很方便挠阁,無須對(duì)現(xiàn)有類庫進(jìn)行任何修改宾肺,符合“開閉原則”。
- 組合模式為樹形結(jié)構(gòu)的面向?qū)ο髮?shí)現(xiàn)提供了一種靈活的解決方案侵俗,通過葉子對(duì)象和容器對(duì)象的遞歸組合锨用,可以形成復(fù)雜的樹形結(jié)構(gòu),但對(duì)樹形結(jié)構(gòu)的控制卻非常簡(jiǎn)單隘谣。
-
組合模式的主要缺點(diǎn)如下:
- 使得設(shè)計(jì)更加復(fù)雜增拥,客戶端需要花更多時(shí)間理清類之間的層次關(guān)系。
- 在增加新構(gòu)件時(shí)很難對(duì)容器中的構(gòu)件類型進(jìn)行限制寻歧。
3 適用場(chǎng)景
- 在具有整體和部分的層次結(jié)構(gòu)中掌栅,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對(duì)待它們码泛。
- 在一個(gè)使用面向?qū)ο笳Z言開發(fā)的系統(tǒng)中需要處理一個(gè)樹形結(jié)構(gòu)猾封。
- 在一個(gè)系統(tǒng)中能夠分離出葉子對(duì)象和容器對(duì)象,而且它們的類型不固定噪珊,需要增加一些新的類型晌缘。
4 總結(jié)
組合對(duì)象的關(guān)鍵在于它定義了一個(gè)抽象構(gòu)建類,它既可表示葉子對(duì)象痢站,也可表示容器對(duì)象磷箕,客戶僅僅需要針對(duì)這個(gè)抽象構(gòu)建進(jìn)行編程,無須知道他是葉子對(duì)象還是容器對(duì)象阵难,都是一致對(duì)待岳枷。
組合模式雖然能夠非常好地處理層次結(jié)構(gòu),也使得客戶端程序變得簡(jiǎn)單呜叫,但是它也使得設(shè)計(jì)變得更加抽象空繁,而且也很難對(duì)容器中的構(gòu)件類型進(jìn)行限制,這會(huì)導(dǎo)致在增加新的構(gòu)件時(shí)會(huì)產(chǎn)生一些問題朱庆。
參考書籍及文章
1.《Java與模式》家厌,電子工業(yè)出版社,閻宏
- 《大話設(shè)計(jì)模式》椎工,清華大學(xué)出版社饭于,程杰
- 《設(shè)計(jì)模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ)》,機(jī)械工業(yè)出版社维蒙,Erich Gamma掰吕,Richard Helm,Ralph Johnson颅痊,John Vlissides
- 《Head First 設(shè)計(jì)模式(中文版)》殖熟,中國(guó)電力出版社
- 《圖說設(shè)計(jì)模式》,https://design-patterns.readthedocs.io/zh_CN/latest/index.html
- 《設(shè)計(jì)模式 | 組合模式及典型應(yīng)用
》斑响,https://juejin.im/post/5bb730e96fb9a05d0c37e66b