Android的设计模式-组合模式

前言

Android的设计模式系列文章介绍,欢迎关注,持续更新中:

Android的设计模式-设计模式的六大原则
一句话总结23种设计模式则
创建型模式:
Android的设计模式-单例模式
Android的设计模式-建造者模式
Android的设计模式-工厂方法模式
Android的设计模式-简单工厂模式
Android的设计模式-抽象工厂模式
Android的设计模式-原型模式
行为型模式:
Android的设计模式-策略模式
Android的设计模式-状态模式
Android的设计模式-责任链模式
Android的设计模式-观察者模式
Android的设计模式-模板方法模式
Android的设计模式-迭代器模式
Android的设计模式-备忘录模式
Android的设计模式-访问者模式
Android的设计模式-中介者模式
Android的设计模式-解释器模式
Android的设计模式-命令模式
结构型模式:
Android的设计模式-代理模式
Android的设计模式-组合模式
Android的设计模式-适配器模式
Android的设计模式-装饰者模式
Android的设计模式-享元模式
Android的设计模式-外观模式
Android的设计模式-桥接模式

1.定义

将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。

2.介绍

  • 组合模式属于结构型模式。
  • 组合模式有时叫做部分—整体模式,主要是描述部分与整体的关系。
  • 组合模式实际上就是个树形结构,一棵树的节点如果没有分支,就是叶子节点;如果存在分支,则是树枝节点。
  • 我们平时遇到的最典型的组合结构就是文件和文件夹了,具体的文件就是叶子节点,而文件夹下还可以存在文件和文件夹,所以文件夹一般是树枝节点。


    文件夹树形结构.png

3.UML类图

组合模式UML类图.jpg
角色说明:
  • Component(抽象组件角色):定义参加组合对象的共有方法和属性,可以定义一些默认的函数或属性。
  • Leaf(叶子节点):叶子没有子节点,因此是组合树的最小构建单元。
  • Composite(树枝节点):定义所有枝干节点的行为,存储子节点,实现相关操作。

4.实现(透明的组合模式)

下面以网站页面为例子,一个页面有多个栏目以及内容,栏目又可以包含子栏目以及具体内容,这就是一个树形结构。如下图:


网站页面结构图.png
4.1 创建抽象组件角色

这里就是一个网站的抽象页面元素:

    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);//添加栏目或者具体内容

        public abstract void rmPageElement(PageElement pageElement);//删除栏目或者具体内容
        
        public abstract void clear();//清空所有元素

        public abstract void print(String placeholder);//打印页面结构
        
        public String getName() {
            return name;
        }
    }
4.2 创建叶子节点

叶子节点继承了抽象组件角色,但是由于没有分支,所以一些添加删除操作是实现不了的。叶子节点都是一些具体的内容,比如具体的音乐内容、视屏内容等等。

    public class Content extends PageElement {//具体内容

        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 创建树枝节点

树枝节点能够删除添加叶子或树枝。

     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() {
        //创建网站根页面 root
        PageElement root = new Column("网站页面");
        //网站页面添加两个栏目:音乐,视屏;以及一个广告内容。
        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);

        //国语,粤语栏目添加具体内容
        chineseMusic.addPageElement(new Content("十年.mp3"));
        cantoneseMusic.addPageElement(new Content("明年今日.mp3"));

        //视频栏目添加具体内容
        video.addPageElement(new Content("唐伯虎点秋香.avi"));

        //打印整个页面的内容
        root.print("");
    }
输出结果:
└──网站页面
   └──音乐
      └──国语
         ──十年.mp3
      └──粤语
         ──明年今日.mp3
   └──视屏
      ──唐伯虎点秋香.avi
   ──广告
4.5 其他说明:

上面的例子可以看到叶子节点其实并不需要添加删除等方法,但由于叶子节点实际上是依赖了抽象组件角色。一方面,这遵循了依赖倒置原则——依赖抽象,而不依赖具体实现;同时,也保证了叶子节点跟树枝节点具体相同的结构,即他们具有同样的方法接口,能够让客户端以一致的方式去处理单个对象和组合对象。但另一方,这违反了单一职责原则接口隔离原则,让 叶子节点继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException 。这实际叫透明的组合模式

5. 安全的组合模式

另外一种组合模式叫安全的组合模式。这种模式客户端在使用的时候必须依赖具体的实现,这违反了依赖倒置原则,但遵循了单一职责原则接口隔离原则

5.1 实现

    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 {//具体内容,只专注自己的职责

        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() {//客户端测试方法
        //依赖具体的实现类Column
        Column root = new Column("网站页面");
       
        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 对比

安全的组合模式将职责区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则和接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以违反单一职责原则和接口隔离原则来换取透明性,但遵循依赖倒置原则,客户端可以直接依赖于抽象组件即可,将叶子和树枝一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。
  一方面,我们写代码时应该遵循各种设计原则,但实际上,有些设计模式原则在使用时会发生冲突,这就需要我们根据实际情况去衡量做出取舍,适合自己的才是最好的。

6. 应用场景

  • 表示对象的部分-整体层次结构时。
  • 从一个整体中能够独立出部分模块或功能时。

7. 优点

  • 高层模块(客户端)调用简单。局部和整体都是同样的结构,客户端无需关心是局部还是整体。
  • 新增节点容易。无需对现有类库进行任何修改,符合开闭原则。

8. 缺点

  • 不同的组合模式实现有不同的缺点,具体看上面的分析。

9. Android中的源码分析

Android源码中,ViewGroupView就是典型的组合模式。

9.1 View类

View相当与叶子节点,里面没有添加删除View等操作。

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

9.2 ViewGroup类

ViewGroup实际上是View的子类,同时ViewGroup中实现了添加删除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
            //具体实现代码略
        }

        @Override
        public void updateViewLayout(View view, android.view.ViewGroup.LayoutParams params) {//更新view 
            //具体实现代码略
        }

        @Override
        public void removeView(View view) {//移除view
            //具体实现代码略
        }

        //其他代码略
    }

9.3 ViewManager接口

实际上ViewGroup中的了添加删除View是实现了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种设计模式则
创建型模式:
Android的设计模式-单例模式
Android的设计模式-建造者模式
Android的设计模式-工厂方法模式
Android的设计模式-简单工厂模式
Android的设计模式-抽象工厂模式
Android的设计模式-原型模式
行为型模式:
Android的设计模式-策略模式
Android的设计模式-状态模式
Android的设计模式-责任链模式
Android的设计模式-观察者模式
Android的设计模式-模板方法模式
Android的设计模式-迭代器模式
Android的设计模式-备忘录模式
Android的设计模式-访问者模式
Android的设计模式-中介者模式
Android的设计模式-解释器模式
Android的设计模式-命令模式
结构型模式:
Android的设计模式-代理模式
Android的设计模式-组合模式
Android的设计模式-适配器模式
Android的设计模式-装饰者模式
Android的设计模式-享元模式
Android的设计模式-外观模式
Android的设计模式-桥接模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容