Android的設計模式-組合模式

前言

Android的設計模式系列文章介紹,歡迎關注,持續(xù)更新中:

Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創(chuàng)建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態(tài)模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式

1.定義

將對象組合成樹形結構以表示“部分-整體”的層次結構,使得用戶對單個對象和組合對象的使用具有一致性。

2.介紹

  • 組合模式屬于結構型模式。
  • 組合模式有時叫做部分—整體模式,主要是描述部分與整體的關系。
  • 組合模式實際上就是個樹形結構,一棵樹的節(jié)點如果沒有分支,就是葉子節(jié)點;如果存在分支,則是樹枝節(jié)點。
  • 我們平時遇到的最典型的組合結構就是文件和文件夾了,具體的文件就是葉子節(jié)點,而文件夾下還可以存在文件和文件夾,所以文件夾一般是樹枝節(jié)點。


    文件夾樹形結構.png

3.UML類圖

組合模式UML類圖.jpg
角色說明:
  • Component(抽象組件角色):定義參加組合對象的共有方法和屬性,可以定義一些默認的函數(shù)或屬性。
  • Leaf(葉子節(jié)點):葉子沒有子節(jié)點,因此是組合樹的最小構建單元。
  • Composite(樹枝節(jié)點):定義所有枝干節(jié)點的行為,存儲子節(jié)點,實現(xiàn)相關操作。

4.實現(xiàn)(透明的組合模式)

下面以網(wǎng)站頁面為例子,一個頁面有多個欄目以及內(nèi)容,欄目又可以包含子欄目以及具體內(nèi)容,這就是一個樹形結構。如下圖:


網(wǎng)站頁面結構圖.png
4.1 創(chuàng)建抽象組件角色

這里就是一個網(wǎng)站的抽象頁面元素:

    public abstract class PageElement {//頁面
        protected List<PageElement> mPageElements = new ArrayList<>();//用來保存頁面元素
        private String name;

        public PageElement(String name) {
            this.name = name;
        }

        public abstract void addPageElement(PageElement pageElement);//添加欄目或者具體內(nèi)容

        public abstract void rmPageElement(PageElement pageElement);//刪除欄目或者具體內(nèi)容
        
        public abstract void clear();//清空所有元素

        public abstract void print(String placeholder);//打印頁面結構
        
        public String getName() {
            return name;
        }
    }
4.2 創(chuàng)建葉子節(jié)點

葉子節(jié)點繼承了抽象組件角色,但是由于沒有分支,所以一些添加刪除操作是實現(xiàn)不了的。葉子節(jié)點都是一些具體的內(nèi)容,比如具體的音樂內(nèi)容、視屏內(nèi)容等等。

    public class Content extends PageElement {//具體內(nèi)容

        public Content(String name) {
            super(name);
        }

        @Override
        public void addPageElement(PageElement pageElement) {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void rmPageElement(PageElement pageElement) {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void clear() {
            throw new UnsupportedOperationException("不支持此操作");
        }

        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "──" + getName());
        }
        
    }
4.3 創(chuàng)建樹枝節(jié)點

樹枝節(jié)點能夠刪除添加葉子或樹枝。

     public class Column extends PageElement {//欄目

        public Column(String name) {
            super(name);
        }

        @Override
        public void addPageElement(PageElement pageElement) {
            mPageElements.add(pageElement);
        }

        @Override
        public void rmPageElement(PageElement pageElement) {
            mPageElements.remove(pageElement);
        }

        @Override
        public void clear() {
            mPageElements.clear();
        }
        
        /**
         * @param placeholder 占位符
         */
        @Override
        public void print(String placeholder) {
            //利用遞歸來打印文件夾結構
            System.out.println(placeholder + "└──" + getName());
            Iterator<PageElement> i = mPageElements.iterator();
            while (i.hasNext()) {
                PageElement pageElement = i.next();
                pageElement.print(placeholder + "   ");
            }
        }

    }
4.4 客戶端測試:
    public void test() {
        //創(chuàng)建網(wǎng)站根頁面 root
        PageElement root = new Column("網(wǎng)站頁面");
        //網(wǎng)站頁面添加兩個欄目:音樂,視屏;以及一個廣告內(nèi)容。
        PageElement music = new Column("音樂");
        PageElement video = new Column("視屏");
        PageElement ad = new Content("廣告");
        root.addPageElement(music);
        root.addPageElement(video);
        root.addPageElement(ad);

        //音樂欄目添加兩個子欄目:國語,粵語
        PageElement chineseMusic = new Column("國語");
        PageElement cantoneseMusic = new Column("粵語");
        music.addPageElement(chineseMusic);
        music.addPageElement(cantoneseMusic);

        //國語,粵語欄目添加具體內(nèi)容
        chineseMusic.addPageElement(new Content("十年.mp3"));
        cantoneseMusic.addPageElement(new Content("明年今日.mp3"));

        //視頻欄目添加具體內(nèi)容
        video.addPageElement(new Content("唐伯虎點秋香.avi"));

        //打印整個頁面的內(nèi)容
        root.print("");
    }
輸出結果:
└──網(wǎng)站頁面
   └──音樂
      └──國語
         ──十年.mp3
      └──粵語
         ──明年今日.mp3
   └──視屏
      ──唐伯虎點秋香.avi
   ──廣告
4.5 其他說明:

上面的例子可以看到葉子節(jié)點其實并不需要添加刪除等方法,但由于葉子節(jié)點實際上是依賴了抽象組件角色。一方面,這遵循了依賴倒置原則——依賴抽象,而不依賴具體實現(xiàn);同時,也保證了葉子節(jié)點跟樹枝節(jié)點具體相同的結構,即他們具有同樣的方法接口,能夠讓客戶端以一致的方式去處理單個對象和組合對象。但另一方,這違反了單一職責原則接口隔離原則,讓 葉子節(jié)點繼承了它本不應該有的方法,并且不太優(yōu)雅的拋出了 UnsupportedOperationException 。這實際叫透明的組合模式

5. 安全的組合模式

另外一種組合模式叫安全的組合模式。這種模式客戶端在使用的時候必須依賴具體的實現(xiàn),這違反了依賴倒置原則,但遵循了單一職責原則接口隔離原則

5.1 實現(xiàn)

    public abstract class PageElement {//頁面
        private String name;

        public PageElement(String name) {
            this.name = name;
        }

        //抽象組件角色去掉增刪等接口

        public abstract void print(String placeholder);

        public String getName() {
            return name;
        }
    }
    
    public class Content extends PageElement {//具體內(nèi)容,只專注自己的職責

        public Content(String name) {
            super(name);
        }
        
        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "──" + getName());
        }
    }
    
    public class Column extends PageElement {//欄目
        private List<PageElement> mPageElements = new ArrayList<>();//用來保存頁面元素

        public Column(String name) {
            super(name);
        }

        public void addPageElement(PageElement pageElement) {
            mPageElements.add(pageElement);
        }

        public void rmPageElement(PageElement pageElement) {
            mPageElements.remove(pageElement);
        }

        public void clear() {
            mPageElements.clear();
        }

        @Override
        public void print(String placeholder) {
            System.out.println(placeholder + "└──" + getName());
            Iterator<PageElement> i = mPageElements.iterator();
            while (i.hasNext()) {
                PageElement pageElement = i.next();
                pageElement.print(placeholder + "   ");
            }
        }

    }
    
    public void test() {//客戶端測試方法
        //依賴具體的實現(xiàn)類Column
        Column root = new Column("網(wǎng)站頁面");
       
        Column music = new Column("音樂");
        Column video = new Column("視屏");
        PageElement ad = new Content("廣告");
        root.addPageElement(music);
        root.addPageElement(video);
        root.addPageElement(ad);

        Column chineseMusic = new Column("國語");
        Column cantoneseMusic = new Column("粵語");
        music.addPageElement(chineseMusic);
        music.addPageElement(cantoneseMusic);

        chineseMusic.addPageElement(new Content("十年.mp3"));
        cantoneseMusic.addPageElement(new Content("明年今日.mp3"));

        video.addPageElement(new Content("唐伯虎點秋香.avi"));

        root.print("");
    }

5.2 對比

安全的組合模式將職責區(qū)分開來放在不同的接口中,這樣一來,設計上就比較安全,也遵循了單一職責原則和接口隔離原則,但是也讓客戶端必須依賴于具體的實現(xiàn);透明的組合模式,以違反單一職責原則和接口隔離原則來換取透明性,但遵循依賴倒置原則,客戶端可以直接依賴于抽象組件即可,將葉子和樹枝一視同仁,也就是說,一個元素究竟是枝干節(jié)點還是葉子節(jié)點,對客戶端是透明的。
  一方面,我們寫代碼時應該遵循各種設計原則,但實際上,有些設計模式原則在使用時會發(fā)生沖突,這就需要我們根據(jù)實際情況去衡量做出取舍,適合自己的才是最好的。

6. 應用場景

  • 表示對象的部分-整體層次結構時。
  • 從一個整體中能夠獨立出部分模塊或功能時。

7. 優(yōu)點

  • 高層模塊(客戶端)調(diào)用簡單。局部和整體都是同樣的結構,客戶端無需關心是局部還是整體。
  • 新增節(jié)點容易。無需對現(xiàn)有類庫進行任何修改,符合開閉原則。

8. 缺點

  • 不同的組合模式實現(xiàn)有不同的缺點,具體看上面的分析。

9. Android中的源碼分析

Android源碼中,ViewGroupView就是典型的組合模式。

9.1 View類

View相當與葉子節(jié)點,里面沒有添加刪除View等操作。

    public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {
        //具體代碼略
    }

9.2 ViewGroup類

ViewGroup實際上是View的子類,同時ViewGroup中實現(xiàn)了添加刪除View等操作,因此可以作為容器存放view。

    public abstract class ViewGroup extends android.view.View implements ViewParent, ViewManager {//繼承View
        @Override
        public void addView(View child, android.view.ViewGroup.LayoutParams params) {//添加view
            //具體實現(xiàn)代碼略
        }

        @Override
        public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {//更新view 
            //具體實現(xiàn)代碼略
        }

        @Override
        public void removeView(View view) {//移除view
            //具體實現(xiàn)代碼略
        }

        //其他代碼略
    }

9.3 ViewManager接口

實際上ViewGroup中的了添加刪除View是實現(xiàn)了ViewManager接口中的方法:

public interface ViewManager{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

9.4 其他

可以看出ViewGroup 和 View 使用的是安全的組合模式,而不是透明的組合模式。

相關文章閱讀
Android的設計模式-設計模式的六大原則
一句話總結23種設計模式則
創(chuàng)建型模式:
Android的設計模式-單例模式
Android的設計模式-建造者模式
Android的設計模式-工廠方法模式
Android的設計模式-簡單工廠模式
Android的設計模式-抽象工廠模式
Android的設計模式-原型模式
行為型模式:
Android的設計模式-策略模式
Android的設計模式-狀態(tài)模式
Android的設計模式-責任鏈模式
Android的設計模式-觀察者模式
Android的設計模式-模板方法模式
Android的設計模式-迭代器模式
Android的設計模式-備忘錄模式
Android的設計模式-訪問者模式
Android的設計模式-中介者模式
Android的設計模式-解釋器模式
Android的設計模式-命令模式
結構型模式:
Android的設計模式-代理模式
Android的設計模式-組合模式
Android的設計模式-適配器模式
Android的設計模式-裝飾者模式
Android的設計模式-享元模式
Android的設計模式-外觀模式
Android的設計模式-橋接模式

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,182評論 6 543
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,489評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,290評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,776評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,510評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,866評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,860評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,036評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,585評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,331評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,536評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,058評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,754評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,154評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,469評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,273評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,505評論 2 379

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