什么是組合模式
組合模式(Composite Pattern):組合多個(gè)對(duì)象形成樹形結(jié)構(gòu)以表示具有“整體—部分”關(guān)系的層次結(jié)構(gòu)。組合模式對(duì)單個(gè)對(duì)象(即葉子對(duì)象)和組合對(duì)象(即容器對(duì)象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對(duì)象結(jié)構(gòu)型模式。
組合模式將對(duì)象組織到樹結(jié)構(gòu)中,可以用來描述整體與部分的關(guān)系,可以使客戶端將單純?cè)嘏c復(fù)合元素同等看待。
樹結(jié)構(gòu)在過程性的編程語言中曾經(jīng)發(fā)揮了巨大的作用,在面向?qū)ο蟮恼Z言中,樹結(jié)構(gòu)也同樣威力巨大。一個(gè)基于繼承的類型的等級(jí)結(jié)構(gòu)便是一個(gè)樹結(jié)構(gòu);一個(gè)基于組合的對(duì)象結(jié)構(gòu)也是一個(gè)樹結(jié)構(gòu)。
在樹形結(jié)構(gòu)中,最頂層的節(jié)點(diǎn)被稱為根節(jié)點(diǎn),根節(jié)點(diǎn)下面可以包含樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn),樹枝節(jié)點(diǎn)下面又可以包含樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn),如下圖所示:
由上圖可以看出,其實(shí)根節(jié)點(diǎn)和樹枝節(jié)點(diǎn)本質(zhì)上屬于同一種數(shù)據(jù)類型,可以作為容器使用;而葉子節(jié)點(diǎn)與樹枝節(jié)點(diǎn)在語義上不屬于用一種類型。但是在組合模式中,會(huì)把樹枝節(jié)點(diǎn)和葉子節(jié)點(diǎn)看作屬于同一種數(shù)據(jù)類型(用統(tǒng)一接口定義),讓它們具備一致行為。
這樣,在組合模式中,整個(gè)樹形結(jié)構(gòu)中的對(duì)象都屬于同一種類型,帶來的好處就是用戶不需要辨別是樹枝節(jié)點(diǎn)還是葉子節(jié)點(diǎn),可以直接進(jìn)行操作,給用戶的使用帶來極大的便利。
模式的結(jié)構(gòu)?
組合模式UML類圖
UML類圖講解:
Component(抽象構(gòu)件):它可以是接口或抽象類,為葉子構(gòu)件和容器構(gòu)件對(duì)象聲明接口,在該角色中可以包含所有子類共有行為的聲明和實(shí)現(xiàn)。在抽象構(gòu)件中定義了訪問及管理它的子構(gòu)件的方法,如增加子構(gòu)件、刪除子構(gòu)件、獲取子構(gòu)件等。
Leaf(葉子構(gòu)件):它在組合結(jié)構(gòu)中表示葉子節(jié)點(diǎn)對(duì)象,葉子節(jié)點(diǎn)沒有子節(jié)點(diǎn),它實(shí)現(xiàn)了在抽象構(gòu)件中定義的行為。對(duì)于那些訪問及管理子構(gòu)件的方法,可以通過異常等方式進(jìn)行處理。
Composite(容器構(gòu)件):它在組合結(jié)構(gòu)中表示容器節(jié)點(diǎn)對(duì)象,容器節(jié)點(diǎn)包含子節(jié)點(diǎn),其子節(jié)點(diǎn)可以是葉子節(jié)點(diǎn),也可以是容器節(jié)點(diǎn),它提供一個(gè)集合用于存儲(chǔ)子節(jié)點(diǎn),實(shí)現(xiàn)了在抽象構(gòu)件中定義的行為,包括那些訪問及管理子構(gòu)件的方法,在其業(yè)務(wù)方法中可以遞歸調(diào)用其子節(jié)點(diǎn)的業(yè)務(wù)方法。
優(yōu)點(diǎn)和缺點(diǎn)
優(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),簡化了客戶端代碼。
在組合模式中增加新的容器構(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)的控制卻非常簡單。
缺點(diǎn)
組合模式的主要缺點(diǎn)如下:
破壞了“單一職責(zé)原則”。
在增加新構(gòu)件時(shí)很難對(duì)容器中的構(gòu)件類型進(jìn)行限制。有時(shí)候我們希望一個(gè)容器中只能有某些特定類型的對(duì)象,例如在某個(gè)文件夾中只能包含文本文件,使用組合模式時(shí),不能依賴類型系統(tǒng)來施加這些約束,因?yàn)樗鼈兌紒碜杂谙嗤某橄髮?,在這種情況下,必須通過在運(yùn)行時(shí)進(jìn)行類型檢查來實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)過程較為復(fù)雜。
組合模式的實(shí)現(xiàn)根據(jù)所實(shí)現(xiàn)接口的區(qū)別分為透明式組合模式和安全式組合模式。
透明式
作為第一種選擇,在Component里面聲明所有的用來管理子類對(duì)象的方法,包括add()、remove(),以及 getChild()方法。這樣做的好處是所有的構(gòu)件類都有相同的接口。在客戶端看來,樹葉類對(duì)象與合成類對(duì)象的區(qū)別起碼在接口層次上消失了,客戶端可以同等地對(duì)待所有的對(duì)象。這就是透明形式的合成模式。
這個(gè)選擇的缺點(diǎn)是不夠安全,因?yàn)闃淙~類對(duì)象和合成類對(duì)象在本質(zhì)上是有區(qū)別的。樹葉類對(duì)象不可能有下一個(gè)層次的對(duì)象,因此add()、remove()以及 getChild()方法沒有意義,但是在編譯時(shí)期不會(huì)出錯(cuò),而只會(huì)在運(yùn)行時(shí)期才會(huì)出錯(cuò)。
透明式的組合模式要求所有的具體構(gòu)件類,不論樹枝構(gòu)件還是樹葉構(gòu)件,都符合一個(gè)固定的接口,類圖如下:
透明式組合模式涉及到抽象構(gòu)件角色、樹葉構(gòu)件角色、樹枝構(gòu)件角色三種模式:
- 抽象構(gòu)件(Component)角色:這是一個(gè)抽象角色,它給參加組合的對(duì)象規(guī)定一個(gè)接口,規(guī)范共有的接口及默認(rèn)行為。這個(gè)接口可以用來管理所有的子對(duì)象,要提供一個(gè)接口以規(guī)范取得和管理下層組件的接口,包括 add()、remove()以及getChild()之類的方法。
- 樹葉構(gòu)件(Leaf〉角色:代表參加組合的樹葉對(duì)象,定義出參加組合的原始對(duì)象的行為。樹葉類會(huì)給出add()、remove()以及getChild()之類的用來管理子類對(duì)象的方法的平庸實(shí)現(xiàn)。
- 樹枝構(gòu)件(Composite)角色:代表參加組合的有子對(duì)象的對(duì)象,定義出這樣的對(duì)象的行為。
我們都見過畫圖軟件,一個(gè)繪圖系統(tǒng)給出各種工具用來描繪線、長方形和原形等基本圖形組成的圖形。一個(gè)復(fù)雜的圖形肯定是有這些基本的圖形組成的。本模式我們就以這為例子來講解。
由于一個(gè)復(fù)雜的圖形是由基本圖形組合而成的,因此,一個(gè)組合的圖形應(yīng)當(dāng)有一個(gè)列表,存儲(chǔ)對(duì)所有的基本圖形對(duì)象的引用。復(fù)合圖形的draw()方法在調(diào)用時(shí),應(yīng)當(dāng)逐一調(diào)用所有列表上的基本圖形對(duì)象的draw()方法。
透明形式的組合模式意味著不僅只有樹枝構(gòu)件角色才配備有管理聚集的方法,樹葉構(gòu)件也有這些方法,雖然樹葉構(gòu)件的這些方法是平庸的。透明式的組合模式的類圖如下:
抽象構(gòu)件角色:
樹枝構(gòu)件角色:
public class Picture extends Graphics {
private Vector items = new Vector(10);
//具體管理方法,增加一個(gè)子構(gòu)件對(duì)象
public void add(Graphics graphics){
items.add(graphics);
}
//刪除一個(gè)子構(gòu)件對(duì)象
public void remove(Graphics graphics){
items.remove(graphics);
}
//返回一個(gè)子構(gòu)件對(duì)象
public .Graphics getChild(int i){
return (Graphics) items.get(i);
}
@Override
public void draw() {
for (int i = 0; i < items.size(); i++) {
Graphics graphics = (Graphics) items.get(i);
graphics.draw();
}
}
}
樹葉構(gòu)件角色:
package com.zeus;
public class Line extends Graphics{
@Override
void draw() {
System.out.println("畫了一條線");
}
@Override
void add() {
}
@Override
void remove() {
}
@Override
Graphics getChild(int i) {
return null;
}
}
package com.zeus;
public class Circle extends Graphics{
@Override
void draw() {
System.out.println("畫了一個(gè)圓形");
}
@Override
void add() {
}
@Override
void remove() {
}
@Override
Graphics getChild(int i) {
return null;
}
}
package com.zeus;
public class Rectangle extends Graphics{
@Override
void draw() {
System.out.println("畫了一個(gè)長方形");
}
@Override
void add() {
}
@Override
void remove() {
}
@Override
Graphics getChild(int i) {
return null;
}
}
測(cè)試:
打印的結(jié)果:
??? 畫了一個(gè)長方形
??? 畫了一條線
??? 畫了一個(gè)長方形
安全式
第二種選擇是在 Composite類里面聲明所有的用來管理子類對(duì)象的方法。這樣的做法是安全的做法,因?yàn)闃淙~類型的對(duì)象根本就沒有管理子類對(duì)象的方法,因此,如果客戶端對(duì)樹葉類對(duì)象使用這些方法時(shí),程序會(huì)在編譯時(shí)期出錯(cuò)。編譯通不過,就不會(huì)出現(xiàn)運(yùn)行時(shí)期錯(cuò)誤。
這個(gè)選擇的缺點(diǎn)是不夠透明,因?yàn)闃淙~類和合成類將具有不同的接口。
安全式的組合模式要求管理具體的方法只出現(xiàn)在樹枝構(gòu)件類中,如下圖所示:
安全式組合模式涉及到抽象構(gòu)件角色、樹葉構(gòu)件角色、樹枝構(gòu)件角色這三個(gè)角色:
- 抽象構(gòu)件角色(Component):這是一個(gè)抽象角色,他給參加組合的對(duì)象定義出公共的接口及其默認(rèn)行為,可以用來管理所有的子對(duì)象。組合對(duì)象通常把它所包含的子對(duì)象當(dāng)作類型為component的對(duì)象,在安全式的組合模式里,構(gòu)件角色并不定義出管理子對(duì)象的方法
- 樹葉構(gòu)件角色(Leaf):樹葉對(duì)象是沒有下級(jí)子對(duì)象的對(duì)象,定義出參加組合的原始對(duì)象的行為
- 樹枝構(gòu)件角色(Composite):代表參加組合的有下級(jí)子對(duì)象的對(duì)象,樹枝構(gòu)件類給出所有的管理子對(duì)象的方法,如add(),remove()以及getChild()等方法
同樣以上面的繪圖系統(tǒng)為例子講解安全式組合模式。安全式組合模式意味著只有樹枝構(gòu)件角色才能配備有管理聚集的方法,而樹葉構(gòu)件角色則沒有這些方法。UML類圖如下:
抽象構(gòu)件角色:
樹枝構(gòu)件角色:
public class Picture extends Graphics{
private Vector items = new Vector(10);
//具體管理方法,增加一個(gè)子構(gòu)件對(duì)象
public void add(Graphics graphics){
items.add(graphics);
}
//刪除一個(gè)子構(gòu)件對(duì)象
public void remove(Graphics graphics){
items.remove(graphics);
}
//返回一個(gè)子構(gòu)件對(duì)象
public Graphics getChild(int i){
return (Graphics) items.get(i);
}
@Override
public void draw() {
for (int i = 0; i < items.size(); i++) {
Graphics graphics = (Graphics) items.get(i);
graphics.draw();
}
}
}
樹葉構(gòu)件角色:
測(cè)試:
打印結(jié)果:
??? 畫了一個(gè)長方形。。。。
??? 畫了一條線。。。。
??? 畫了一個(gè)長方形。。。。
適用環(huán)境
在以下情況下可以考慮使用組合模式:
-
在具有整體和部分的層次結(jié)構(gòu)中,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對(duì)待它們。
-
在一個(gè)使用面向?qū)ο笳Z言開發(fā)的系統(tǒng)中需要處理一個(gè)樹形結(jié)構(gòu)。文章來源:http://www.zghlxwxcb.cn/news/detail-678410.html
-
在一個(gè)系統(tǒng)中能夠分離出葉子對(duì)象和容器對(duì)象,而且它們的類型不固定,需要增加一些新的類型。文章來源地址http://www.zghlxwxcb.cn/news/detail-678410.html
到了這里,關(guān)于設(shè)計(jì)模式——組合模式的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!