設(shè)計(jì)模式之十二——組合模式

原文傳送門

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)景

  1. 在具有整體和部分的層次結(jié)構(gòu)中掌栅,希望通過一種方式忽略整體與部分的差異,客戶端可以一致地對(duì)待它們码泛。
  2. 在一個(gè)使用面向?qū)ο笳Z言開發(fā)的系統(tǒng)中需要處理一個(gè)樹形結(jié)構(gòu)猾封。
  3. 在一個(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è)出版社,閻宏

  1. 《大話設(shè)計(jì)模式》椎工,清華大學(xué)出版社饭于,程杰
  2. 《設(shè)計(jì)模式——可復(fù)用面向?qū)ο筌浖幕A(chǔ)》,機(jī)械工業(yè)出版社维蒙,Erich Gamma掰吕,Richard Helm,Ralph Johnson颅痊,John Vlissides
  3. 《Head First 設(shè)計(jì)模式(中文版)》殖熟,中國(guó)電力出版社
  4. 《圖說設(shè)計(jì)模式》,https://design-patterns.readthedocs.io/zh_CN/latest/index.html
  5. 《設(shè)計(jì)模式 | 組合模式及典型應(yīng)用
    》斑响,https://juejin.im/post/5bb730e96fb9a05d0c37e66b
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末菱属,一起剝皮案震驚了整個(gè)濱河市钳榨,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纽门,老刑警劉巖薛耻,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赏陵,居然都是意外死亡饼齿,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門蝙搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缕溉,“玉大人,你說我怎么就攤上這事吃型≈づ福” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵勤晚,是天一觀的道長(zhǎng)敌土。 經(jīng)常有香客問我,道長(zhǎng)运翼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任兴枯,我火速辦了婚禮血淌,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘财剖。我一直安慰自己悠夯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開白布躺坟。 她就那樣靜靜地躺著沦补,像睡著了一般。 火紅的嫁衣襯著肌膚如雪咪橙。 梳的紋絲不亂的頭發(fā)上夕膀,一...
    開封第一講書人閱讀 51,370評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音美侦,去河邊找鬼产舞。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菠剩,可吹牛的內(nèi)容都是我干的易猫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼具壮,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼准颓!你這毒婦竟也來了哈蝇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤攘已,失蹤者是張志新(化名)和其女友劉穎炮赦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體贯被,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡眼五,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彤灶。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片看幼。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖幌陕,靈堂內(nèi)的尸體忽然破棺而出诵姜,到底是詐尸還是另有隱情,我是刑警寧澤搏熄,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布棚唆,位于F島的核電站,受9級(jí)特大地震影響心例,放射性物質(zhì)發(fā)生泄漏宵凌。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一止后、第九天 我趴在偏房一處隱蔽的房頂上張望瞎惫。 院中可真熱鬧,春花似錦译株、人聲如沸瓜喇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乘寒。三九已至,卻和暖如春匪补,著一層夾襖步出監(jiān)牢的瞬間伞辛,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工夯缺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留始锚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓喳逛,卻偏偏與公主長(zhǎng)得像纬乍,于是被迫代替她去往敵國(guó)和親菩帝。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诉瓦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 一. 問自己三個(gè)問題,并將這些思考記錄下來殿怜。 1.第二周冥想練習(xí),你最大的收獲是什么曙砂? 通過第二周的學(xué)習(xí)與聯(lián)系头谜,知...
    奉岳閱讀 305評(píng)論 0 0
  • 《道德經(jīng)》以“柔”的思想核心詮釋出“自然”、“無為”鸠澈、“道”的思想體系柱告。應(yīng)該按照事物本來的生長(zhǎng)樣子,順應(yīng)事物的自然...
    百家和鳴閱讀 567評(píng)論 0 0
  • 都說六月的天笑陈,孩子的臉际度,沒成想這十二月的天也是說變就變,昨天早上還出太陽涵妥,中午吃飯就大雪紛飛了乖菱,怎么說也是今冬第...
    拾一片光陰閱讀 161評(píng)論 0 0
  • 她住在村西,一生勞碌 晚年還拾撿破爛 每一片塑料袋都要在衣襟上揩一下 晴天里再把它們扒拉開曬太陽 她待這些廢品像兒...
    改變自己369閱讀 336評(píng)論 0 2