概念
合成模式屬于對(duì)象的結(jié)構(gòu)模式昧碉,有時(shí)又叫做“部分——整體”模式旗芬。合成模式將對(duì)象組織到樹結(jié)構(gòu)中踱启,可以用來(lái)描述整體與部分的關(guān)系俏讹。合成模式可以使客戶端將單純?cè)嘏c復(fù)合元素同等看待。
合成模式
合成模式把部分和整體的關(guān)系用樹結(jié)構(gòu)表示出來(lái)纵势。合成模式使得客戶端把一個(gè)個(gè)單獨(dú)的成分對(duì)象和由它們復(fù)合而成的合成對(duì)象同等看待踱阿。
比如,一個(gè)文件系統(tǒng)就是一個(gè)典型的合成模式系統(tǒng)钦铁。下圖是常見(jiàn)的計(jì)算機(jī)XP文件系統(tǒng)的一部分扫茅。
從上圖可以看出,文件系統(tǒng)是一個(gè)樹結(jié)構(gòu)育瓜,樹上長(zhǎng)有節(jié)點(diǎn)。樹的節(jié)點(diǎn)有兩種栽烂,一種是樹枝節(jié)點(diǎn)躏仇,即目錄恋脚,有內(nèi)部樹結(jié)構(gòu),在圖中涂有顏色焰手;另一種是文件糟描,即樹葉節(jié)點(diǎn),沒(méi)有內(nèi)部樹結(jié)構(gòu)书妻。
顯然船响,可以把目錄和文件當(dāng)做同一種對(duì)象同等對(duì)待和處理,這也就是合成模式的應(yīng)用躲履。
合成模式可以不提供父對(duì)象的管理方法见间,但是合成模式必須在合適的地方提供子對(duì)象的管理方法,諸如:add()工猜、remove()米诉、以及getChild()等。
合成模式的實(shí)現(xiàn)根據(jù)所實(shí)現(xiàn)接口的區(qū)別分為兩種形式篷帅,分別稱為安全式和透明式史侣。
安全式合成模式的結(jié)構(gòu)
安全模式的合成模式要求管理聚集的方法只出現(xiàn)在樹枝構(gòu)件類中,而不出現(xiàn)在樹葉構(gòu)件類中魏身。
這種形式涉及到三個(gè)角色:
● 抽象構(gòu)件(Component)角色:這是一個(gè)抽象角色惊橱,它給參加組合的對(duì)象定義出公共的接口及其默認(rèn)行為,可以用來(lái)管理所有的子對(duì)象箭昵。合成對(duì)象通常把它所包含的子對(duì)象當(dāng)做類型為Component的對(duì)象税朴。在安全式的合成模式里,構(gòu)件角色并不定義出管理子對(duì)象的方法宙枷,這一定義由樹枝構(gòu)件對(duì)象給出掉房。
● 樹葉構(gòu)件(Leaf)角色:樹葉對(duì)象是沒(méi)有下級(jí)子對(duì)象的對(duì)象,定義出參加組合的原始對(duì)象的行為慰丛。
● 樹枝構(gòu)件(Composite)角色:代表參加組合的有下級(jí)子對(duì)象的對(duì)象卓囚。樹枝構(gòu)件類給出所有的管理子對(duì)象的方法,如add()诅病、remove()以及getChild()哪亿。
源代碼
/**
* 抽象構(gòu)建,使用接口來(lái)定義贤笆,只定義出葉子節(jié)點(diǎn)和樹枝節(jié)點(diǎn)共同的方法
*/
public interface Component {
/**
* 展示節(jié)點(diǎn)名稱
*/
public void showName(String parentName);
}
/**
* 葉子節(jié)點(diǎn)
*/
public class Leaf implements Component {
//節(jié)點(diǎn)名稱
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void showName(String parentName) {
System.out.println(parentName + "-" + name);
}
}
/**
* 樹枝節(jié)點(diǎn)蝇棉,實(shí)現(xiàn)了Component接口,除了實(shí)現(xiàn)接口方法外芥永,還有自己的管理子節(jié)點(diǎn)的方法
*/
public class Composite implements Component {
//節(jié)點(diǎn)名稱
private String name;
public Composite(String name) {
this.name = name;
}
//子節(jié)點(diǎn)
private List<Component> childList = new ArrayList<Component>();
//添加節(jié)點(diǎn)
public void addChild(Component child) {
childList.add(child);
}
//刪除節(jié)點(diǎn)
public void removeChild(int index) {
childList.remove(index);
}
@Override
public void showName(String parentName) {
System.out.println(parentName + "-" + name);
if(this.childList != null && this.childList.size() > 0){
for(Component component: this.childList){
component.showName(parentName + "-" + name);
}
}
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//樹枝節(jié)點(diǎn)必須指明對(duì)象為Composite篡殷,而不能是接口對(duì)象,因?yàn)榻涌趯?duì)象沒(méi)有方法操作子節(jié)點(diǎn)
Composite root = new Composite("服裝");
Composite boy = new Composite("男裝");
Composite girl = new Composite("女裝");
Component boy1 = new Leaf("短袖");
Component boy2 = new Leaf("沙灘褲");
Component girl1 = new Leaf("胸罩");
Component girl2 = new Leaf("內(nèi)褲");
boy.addChild(boy1);
boy.addChild(boy2);
girl.addChild(girl1);
girl.addChild(girl2);
root.addChild(boy);
root.addChild(girl);
root.showName("");
}
}
可以看出埋涧,樹枝構(gòu)件類(Composite)給出了addChild()板辽、removeChild()以及getChild()等方法的聲明和實(shí)現(xiàn)奇瘦,而樹葉構(gòu)件類則沒(méi)有給出這些方法的聲明或?qū)崿F(xiàn)。這樣的做法是安全的做法劲弦,由于這個(gè)特點(diǎn)耳标,客戶端應(yīng)用程序不可能錯(cuò)誤地調(diào)用樹葉構(gòu)件的聚集方法,因?yàn)闃淙~構(gòu)件沒(méi)有這些方法邑跪,調(diào)用會(huì)導(dǎo)致編譯錯(cuò)誤次坡。
安全式合成模式的缺點(diǎn)是不夠透明,因?yàn)闃淙~類和樹枝類將具有不同的接口画畅。
透明式合成模式的結(jié)構(gòu)
與安全式的合成模式不同的是砸琅,透明式的合成模式要求所有的具體構(gòu)件類,不論樹枝構(gòu)件還是樹葉構(gòu)件夜赵,均符合一個(gè)固定接口明棍。
源代碼
/**
* 抽象構(gòu)建,使用抽象類來(lái)實(shí)現(xiàn)寇僧,子節(jié)點(diǎn)和樹枝節(jié)點(diǎn)共有的方法用抽象方法定義摊腋,
* 樹枝節(jié)點(diǎn)獨(dú)有的方法用普通方法實(shí)現(xiàn),并給出缺省實(shí)現(xiàn)
*/
public abstract class Component {
/**
* 添加子節(jié)點(diǎn)
* @param component
*/
public void addChild(Component component) {
/**
* 缺省實(shí)現(xiàn)嘁傀,拋出異常兴蒸,因?yàn)槿~子對(duì)象沒(méi)有此功能
* 或者子組件沒(méi)有實(shí)現(xiàn)這個(gè)功能
*/
throw new UnsupportedOperationException("對(duì)象不支持此方法");
}
/**
* 刪除子節(jié)點(diǎn)
* @param index
*/
public void removeChild(int index) {
/**
* 缺省實(shí)現(xiàn),拋出異常细办,因?yàn)槿~子對(duì)象沒(méi)有此功能
* 或者子組件沒(méi)有實(shí)現(xiàn)這個(gè)功能
*/
throw new UnsupportedOperationException("對(duì)象不支持此方法");
}
/**
* 展示節(jié)點(diǎn)名稱
*/
public abstract void showName(String parentName);
}
/**
* 葉子節(jié)點(diǎn)
*/
public class Leaf extends Component {
//節(jié)點(diǎn)名稱
private String name;
public Leaf(String name) {
this.name = name;
}
@Override
public void showName(String parentName) {
System.out.println(parentName + "-" + name);
}
}
/**
* 樹枝節(jié)點(diǎn)橙凳,繼承抽象類,重寫方法
*/
public class Composite extends Component {
//節(jié)點(diǎn)名稱
private String name;
public Composite(String name) {
this.name = name;
}
//子節(jié)點(diǎn)
private List<Component> childList = new ArrayList<Component>();
//添加節(jié)點(diǎn)
@Override
public void addChild(Component child) {
childList.add(child);
}
//刪除節(jié)點(diǎn)
@Override
public void removeChild(int index) {
childList.remove(index);
}
@Override
public void showName(String parentName) {
System.out.println(parentName + "-" + name);
if(this.childList != null && this.childList.size() > 0){
for(Component component: this.childList){
component.showName(parentName + "-" + name);
}
}
}
}
/**
* 客戶端
*/
public class Client {
public static void main(String[] args) {
//由于是透明式組合模式笑撞,所有方法在抽象類中均有定義岛啸,所以所有的類型都可以為抽象類
Component root = new Composite("服裝");
Component boy = new Composite("男裝");
Component girl = new Composite("女裝");
Component boy1 = new Leaf("短袖");
Component boy2 = new Leaf("沙灘褲");
Component girl1 = new Leaf("胸罩");
Component girl2 = new Leaf("內(nèi)褲");
boy.addChild(boy1);
boy.addChild(boy2);
girl.addChild(girl1);
girl.addChild(girl2);
root.addChild(boy);
root.addChild(girl);
root.showName("");
}
}
可以看出,客戶端無(wú)需再區(qū)分操作的是樹枝對(duì)象(Composite)還是樹葉對(duì)象(Leaf)了茴肥;對(duì)于客戶端而言坚踩,操作的都是Component對(duì)象。
兩種實(shí)現(xiàn)方法的選擇
這里所說(shuō)的安全性合成模式是指:從客戶端使用合成模式上看是否更安全瓤狐,如果是安全的瞬铸,那么就不會(huì)有發(fā)生誤操作的可能,能訪問(wèn)的方法都是被支持的础锐。
這里所說(shuō)的透明性合成模式是指:從客戶端使用合成模式上嗓节,是否需要區(qū)分到底是“樹枝對(duì)象”還是“樹葉對(duì)象”。如果是透明的皆警,那就不用區(qū)分拦宣,對(duì)于客戶而言,都是Compoent對(duì)象,具體的類型對(duì)于客戶端而言是透明的鸵隧,是無(wú)須關(guān)心的桐愉。
對(duì)于合成模式而言,在安全性和透明性上掰派,會(huì)更看重透明性,畢竟合成模式的目的是:讓客戶端不再區(qū)分操作的是樹枝對(duì)象還是樹葉對(duì)象左痢,而是以一個(gè)統(tǒng)一的方式來(lái)操作靡羡。
而且對(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)方式杭煎。