設(shè)計模式之組合模式

一、簡介

組合模式是一種結(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)不需要的方法可以使用空處理或者異常報告的方式來解決。

四、參考

設(shè)計模式之組合模式
設(shè)計模式讀書筆記-----組合模式

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容