一、簡介
組合模式是一種結(jié)構(gòu)型模式,允許我們將對象組合成樹形結(jié)構(gòu)來表現(xiàn)”部分-整體“的層次結(jié)構(gòu),同時使得客戶能夠以一致的方式處理單個對象(葉子對象)和組合結(jié)構(gòu)(容器對象),也就是使用時不需要區(qū)分當(dāng)前對象是葉子對象還是容器對象,因為它們定義了相同的功能。
組合模式中包含三類角色:
- 組合部件(Component):它是一個抽象角色,為要組合的對象(即葉子對象和容器對象)提供統(tǒng)一的接口。因此即可以表示葉子節(jié)點也可以表示枝節(jié)點。
- 葉子對象(Leaf):在樹形結(jié)構(gòu)中表示葉子節(jié)點,不能包含子節(jié)點
- 容器對象(Composite):在樹形結(jié)構(gòu)中表示枝節(jié)點,用來存儲部件,實現(xiàn)在Component接口中的有關(guān)操作,如增加(add)和刪除(remove)子部件(即子節(jié)點)。
從模式結(jié)構(gòu)中我們看出了葉子對象和容器對象都實現(xiàn)Component接口,這也是能夠?qū)⑷~子對象和容器對象一致對待的關(guān)鍵所在。
優(yōu)點:
- 客戶端調(diào)用簡單,客戶端可以一致的使用組合結(jié)構(gòu)或其中單個對象
- 定義了包含葉子對象和容器對象的類層次結(jié)構(gòu),葉子對象可以被組合成更復(fù)雜的容器對象,而這個容器對象又可以被組合,這樣不斷遞歸下去,可以形成復(fù)雜的樹形結(jié)構(gòu)。
- 更容易在組合結(jié)構(gòu)內(nèi)加入新的對象構(gòu)件
缺點:
- 使得設(shè)計更加復(fù)雜??蛻舳诵枰ǜ鄷r間理清類之間的層次關(guān)系。
- 而且抽象的組合部件中定義的方法并不都是葉子對象需要的
二、使用場景
比如一個文件夾(容器對象)下可以包含多個文件(葉子對象),而這個文件夾又可以被其他文件夾包含,這樣不斷的遞歸下去,形成復(fù)雜的樹形結(jié)構(gòu)。并且有時為了方便調(diào)用,我們希望不管是對于其中的文件還是文件夾都可以進行相同的操作,以一致的方式來處理他們,這時可以使用組合模式。
也就是:
- 當(dāng)想表達對象的部分-整體的層次結(jié)構(gòu)時。
- 用戶希望忽略組合對象與單個對象的不同,可以統(tǒng)一地使用組合結(jié)構(gòu)中的所有對象時。
三、舉例:
透明式的組合模式
?在Component中聲明所有來管理子對象的方法,其中包括add,remove等。這樣實現(xiàn)Component接口的所有子類都具備了Add和Remove方法。
?這樣做的好處在于葉節(jié)點和枝節(jié)點對于外界沒有區(qū)別,它們具備完全一致的接口。弊端在于雖然客戶端對葉節(jié)點和枝節(jié)點是一致的,但葉節(jié)點并不具備add和remove的功 能,因而對它們的實現(xiàn)是沒有意義的
//1.首先定義將要被組合的抽象部件(Component),用于訪問和管理Component的子部件
abstract class AbstractFile{
protected String name;
public AbstractFile(String name){
this.name=name;
}
//增加一個子部件
public abstract void add(AbstractFile file);
//移除一個子部件
public abstract void remove(AbstractFile file);
//瀏覽當(dāng)前部件
public abstract void display();
}
//2.定義一類葉子對象(Leaf)
class TextFile extends AbstractFile{
public TextFile(String name) {
super(name);
}
/*
* 由于葉子節(jié)點沒有子節(jié)點,所以add和remove方法對它來說沒有意義,但它繼承自AbstractFile,
* 這樣做可以消除葉節(jié)點和枝節(jié)點對象在抽象層次的區(qū)別,它們具備完全一致的接口。
*/
@Override
public void add(AbstractFile file) {
System.out.println("不能添加一個部件到葉子中");
}
// 實現(xiàn)它沒有意義,只是提供了一個一致的調(diào)用接口
@Override
public void remove(AbstractFile file) {
System.out.println("不能從葉子中移除一個部件");
}
@Override
public void display() {
System.out.println("這是文本文件,文件名:" + super.name);
}
}
//3.定義一類容器對象(枝節(jié)點Composite)
class FileFolder extends AbstractFile{
//一個子對象集合,用來存儲其下屬的枝節(jié)點和葉節(jié)點
private List<AbstractFile>children=new ArrayList<>();
public FileFolder(String name) {
super(name);
}
@Override
public void add(AbstractFile file) {
children.add(file);
}
@Override
public void remove(AbstractFile file) {
children.remove(file);
}
@Override
public void display() {
System.out.println(super.name+"文件夾中包含:");
for(AbstractFile file:children){
System.out.print(" ");
file.display();
}
}
}
//測試:
public class 組合模式 {
public static void main(String[] args) {
/*
* 我們定義一個hwj文件夾,它包含h文件和wj子文件夾,而wj子文件夾中又包含w和j文件
*/
//首先,定義一個hwj文件夾
AbstractFile hwjFolder=new FileFolder("hwj");
//定義一個h文件和wj子文件夾,并把它們加入到hwj文件夾中
AbstractFile hFile=new TextFile("h");
AbstractFile wjFolder=new FileFolder("wj");
hwjFolder.add(hFile);
hwjFolder.add(wjFolder);
//定義一個w文件和j文件,并把它們加入到wj子文件夾中
AbstractFile wFile=new TextFile("w");
AbstractFile jFile=new TextFile("j");
wjFolder.add(wFile);
wjFolder.add(jFile);
//顯示hwj文件夾中包含的所有文件
hwjFolder.display();
}
}
測試結(jié)果
hwj文件夾中包含:
這是文本文件,文件名:h
wj文件夾中包含:
這是文本文件,文件名:w
這是文本文件,文件名:j
安全式的組合模式
?在Component中不去聲明add和remove方法,那么子類的Leaf就不需要實現(xiàn)它不需要的方法,而是在Composit聲明所有用來管理子類對象的方法。。
?這樣做雖然使得葉節(jié)點無需在實現(xiàn)自己不需要的方法,但是對于客戶端來說,必須對葉節(jié)點和枝節(jié)點進行判定,為客戶端的使用帶來不便。
//1.首先定義將要被組合的抽象部件(Component),用于訪問和管理Component的子部件
abstract class AbstractFile{
protected String name;
public AbstractFile(String name){
this.name=name;
}
//瀏覽當(dāng)前部件
public abstract void display();
}
//2.定義一類葉子對象(Leaf)
class TextFile extends AbstractFile{
public TextFile(String name) {
super(name);
}
@Override
public void display() {
System.out.println("這是文本文件,文件名:" + super.name);
}
}
//3.定義一類容器對象(枝節(jié)點Composite),用來存儲部件,實現(xiàn)在Component接口中對子部件有關(guān)的操作
class FileFolder extends AbstractFile{
private List<AbstractFile>children=new ArrayList<>();
public FileFolder(String name) {
super(name);
}
public void add(AbstractFile file) {
children.add(file);
}
public void remove(AbstractFile file) {
children.remove(file);
}
@Override
public void display() {
System.out.println(super.name+"文件夾中包含:");
for(AbstractFile file:children){
System.out.print(" ");
file.display();
}
}
}
注:《設(shè)計模式》一書中提倡:相對于安全性,我們比較強調(diào)透明性。對于第一種方式中葉子節(jié)點內(nèi)不需要的方法可以使用空處理或者異常報告的方式來解決。