java/android 设计模式学习笔记(12)---组合模式

  这篇我们来介绍一下组合模式(Composite Pattern),它也称为部分整体模式(Part-Whole Pattern),结构型模式之一。组合模式比较简单,它将一组相似的对象看作一个对象处理,并根据一个树状结构来组合对象,然后提供一个统一的方法去访问相应的对象,以此忽略掉对象与对象集合之间的差别。这个最典型的例子就是数据结构中的树了,如果一个节点有子节点,那么它就是枝干节点,如果没有子节点,那么它就是叶子节点,那么怎么把枝干节点和叶子节点统一当作一种对象处理呢?这就需要用到组合模式了。

  

  转载请注明出处:http://blog.csdn.net/self_study/article/details/51761709

  PS:对技术感兴趣的同鞋加群544645972一起交流。

设计模式总目录

  java/android 设计模式学习笔记目录

特点

  组合模式允许你将对象组合成树形结构来表现“整体/部分”层次结构,并且能够让客户端以一致的方式处理个别对象以及组合对象。

  组合模式让我们能用树形方式创建对象的结构,树里面包含了组合构件以及叶子构件的对象,而且能够把相同的操作应用在组合构件和叶子构件上,换句话说,在大多数情况下我们可以忽略组合对象和叶子对象之间的差别。组合模式使用的场景:

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

UML类图

  组合模式在实际使用中会有两种情况:安全的组合模式与透明的组合模式。

安全的组合模式

  我们先来看看安全组合模式的 uml 类图:

  

可以看到组合模式有 4 个角色:

  • Component:抽象根节点,为组合中的对象声明接口行为,是所有节点的抽象。在适当的情况下,实现所有类共有接口的缺省行为。声明一个接口用于访问和管理 Component 的子节点。可在递归结构中定义一个接口,用于访问一个父节点,并在合适的情况下实现它;
  • Composite:增加定义枝干节点的行为,存储子节点,实现 Component 接口中的有关的操作;
  • Leaf:在组合中表示叶子结点对象,叶子节点没有子节点,实现 Component 接口中的全部操作;
  • Client:通过 Component,Composite 和 Leaf 类操纵组合节点对象。

  据此我们可以写出安全组合模式的通用代码:

Component.class

public abstract class Component {
    public abstract void operation();
}

Composite.class

public class Composite extends Component{

    private ArrayList<Component> componentList = new ArrayList<>();

    @Override
    public void operation() {
        Log.e("shawn", "this is composite " + this + " -------start");
        for (Component component : componentList) {
            component.operation();
        }
        Log.e("shawn", "this is composite " + this + " -------end");
    }

    public void add(Component child) {
        componentList.add(child);
    }

    public void remove(Component child) {
        componentList.remove(child);
    }

    public Component getChild(int position) {
        return componentList.get(position);
    }
}

Leaf.class

public class Leaf extends Component{
    @Override
    public void operation() {
        Log.e("shawn", "this if leaf " + this);
    }
}

Client 测试代码:

Composite root = new Composite();

Leaf leaf1 = new Leaf();
Composite branch = new Composite();
root.add(leaf1);
root.add(branch);

Leaf leaf2 = new Leaf();
branch.add(leaf2);

root.operation();
break;

最后输出结果:

com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------start
com.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@1d7d4031
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------start
com.android.compositepattern E/shawn: this if leaf com.android.compositepattern.composite.Leaf@5dae497
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@ec97316 -------end
com.android.compositepattern E/shawn: this is composite com.android.compositepattern.composite.Composite@a37f4d8 -------end

代码很简单,结果就是一个简单的树形结构,但是仔细看看客户端代码,就能发现它违反了 6 个设计模式原则中依赖倒置原则,客户端不应该直接依赖于具体实现,而应该依赖于抽象,既然是面向接口编程,就应该把更多的焦点放在接口的设计上,于是这样就产生了透明的组合模式。

透明的组合模式

  来看看透明的组合模式 uml 类图:

  

和安全的组合模式差异就是在将 Composite 的操作放到了 Component 中,这就造成 Leaf 角色也要实现 Component 中的所有方法。实现的代码做出相应改变:

Component.class

public interface Component {
    void operation();

    void add(Component child);

    void remove(Component child);

    Component getChild(int position);
}

Composite.class

public class Composite implements Component{

    private ArrayList<Component> componentList = new ArrayList<>();

    @Override
    public void operation() {
        Log.e("shawn", "this is composite " + this + " -------start");
        for (Component component : componentList) {
            component.operation();
        }
        Log.e("shawn", "this is composite " + this + " -------end");
    }

    @Override
    public void add(Component child) {
        componentList.add(child);
    }

    @Override
    public void remove(Component child) {
        componentList.remove(child);
    }

    @Override
    public Component getChild(int position) {
        return componentList.get(position);
    }
}

Leaf.class

public class Leaf implements Component {
    @Override
    public void operation() {
        Log.e("shawn", "this if leaf " + this);
    }

    @Override
    public void add(Component child) {
        throw new UnsupportedOperationException("leaf can‘t add child");
    }

    @Override
    public void remove(Component child) {
        throw new UnsupportedOperationException("leaf can‘t remove child");
    }

    @Override
    public Component getChild(int position) {
        throw new UnsupportedOperationException("leaf doesn‘t have any child");
    }
}

Client 测试代码

Component root = new Composite();

Component leaf1 = new Leaf();
Component branch = new Composite();
root.add(leaf1);
root.add(branch);

Component leaf2 = new Leaf();
branch.add(leaf2);

root.operation();

最后产生的结果是一样的,由于是在 Component 类中定义了所有的行为,所以客户端就不用直接依赖于具体 Composite 和 Leaf 类的实现,遵循了依赖倒置原则——依赖抽象,而不依赖具体实现。但是也违反了单一职责原则接口隔离原则,让 Leaf 类继承了它本不应该有的方法,并且不太优雅的抛出了 UnsupportedOperationException ,这样做的目的就是为了客户端可以透明的去调用对应组件的方法,将枝干节点和子节点一视同仁。

  另外,将 Component 写成一个虚基类,并且实现所有的 Composite 方法,而且默认都抛出异常,只让 Composite 去覆盖重写父类的方法,而 Leaf 类就不需要去实现 Composite 的相关方法,这么去实现当然也是可以的。

对比

  安全的组合模式将责任区分开来放在不同的接口中,这样一来,设计上就比较安全,也遵循了单一职责原则接口隔离原则,但是也让客户端必须依赖于具体的实现;透明的组合模式,以单一职责原则接口隔离原则原则换取透明性,遵循依赖倒置原则,客户端就直接依赖于 Component 抽象即可,将 Composite 和 Leaf 一视同仁,也就是说,一个元素究竟是枝干节点还是叶子节点,对客户端是透明的。

  所以这是一个很典型的折衷案例,尽管我们受到设计原则的指导,但是我们总是需要观察某原则对我们的设计所造成的影响。有时候这个需要去根据实际案例去分析,毕竟有些时候 6 种设计模式原则在实际使用过程中是会冲突的,是让客户端每次使用的时候都去先检查类型还是赋予子节点不应该有的行为,这都取决于设计者的观点,总体而言,这两种方案都是可行的。

示例与源码

  组合模式在实际生活过程中的例子就数不胜数了,比如菜单、文件夹等等。我们这就以 Android 中非常经典的实现为例来分析一下。View 和 ViewGroup 想必应该都非常熟悉,其实他们用到的就是组合模式,我们先来看看他们之间的 uml 类图:

  

ViewManager 这个类在java/android 设计模式学习笔记(8)—桥接模式中提到过,WindowManager 也继承了该类:

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can‘t be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

只定义了关于 View 操作的三个方法。ViewParent 类是用来定义一个 父 View 角色所具有的职责,在 Android 中,一般能成为父 View 的也只有 ViewGroup:

/**
 * Defines the responsibilities for a class that will be a parent of a View.
 * This is the API that a view sees when it wants to interact with its parent.
 *
 */
public interface ViewParent {
    /**
     * Called when something has changed which has invalidated the layout of a
     * child of this view parent. This will schedule a layout pass of the view
     * tree.
     */
    public void requestLayout();

    /**
     * Indicates whether layout was requested on this view parent.
     *
     * @return true if layout was requested, false otherwise
     */
    public boolean isLayoutRequested();
   ....
}

从 uml 类图中可以注意到一点,ViewGroup 和 View 使用的安全的组合模式,而不是透明的组合模式,怪不得有时候使用前需要将 View 强转成 ViewGroup 。

总结

  使用组合模式,我们能把相同的操作应用在组合和个别对象上,换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。组合模式适用于一些界面 UI 的结构设计上,典型的例子就是Android,iOS 和 Java 等都提供了相应的 UI 框架。

  组合模式的优点:

  • 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让高层模块忽略了层次的差异,方便对整个层次结构进行控制;
  • 高层模块可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是组合结构,简化了高层模块的代码。
  • 在组合模式中增加新的枝干构件和叶子构件都很方便,无需对现有类库进行任何修改,符合“开闭原则”;
  • 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和枝干对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

  组合模式的缺点:在新增构件时不好对枝干中的构建类型进行限制,不能依赖类型系统来施加这些约束,因为在大多数情况下,他们都来自于相同的抽象层,此时,必须进行类型检查来实现,这个实现过程较为复杂。

源码下载

  https://github.com/zhaozepeng/Design-Patterns/tree/master/CompositePattern

引用

https://en.wikipedia.org/wiki/Composite_pattern

http://blog.csdn.net/jason0539/article/details/22642281

http://haolloyin.blog.51cto.com/1177454/347308/

时间: 2024-08-01 10:44:12

java/android 设计模式学习笔记(12)---组合模式的相关文章

java/android 设计模式学习笔记(7)---装饰者模式

这篇将会介绍装饰者模式(Decorator Pattern),装饰者模式也称为包装模式(Wrapper Pattern),结构型模式之一,其使用一种对客户端透明的方式来动态的扩展对象的功能,同时它也是继承关系的一种替代方案之一,但比继承更加灵活.在现实生活中也可以看到很多装饰者模式的例子,或者可以大胆的说装饰者模式无处不在,就拿一件东西来说,可以给它披上无数层不一样的外壳,但是这件东西还是这件东西,外壳不过是用来扩展这个东西的功能而已,这就是装饰者模式,装饰者的这个角色也许各不相同但是被装饰的对

java/android 设计模式学习笔记(16)---命令模式

这篇博客我们来介绍一下命令模式(Command Pattern),它是行为型设计模式之一.命令模式相对于其他的设计模式更为灵活多变,我们接触比较多的命令模式个例无非就是程序菜单命令,如在操作系统中,我们点击关机命令,系统就会执行一系列的操作,如先是暂停处理事件,保存系统的一些配置,然后结束程序进程,最后调用内核命令关闭计算机等,对于这一系列的命令,用户不用去管,用户只需点击系统的关机按钮即可完成如上一系列的命令.而我们的命令模式其实也与之相同,将一系列的方法调用封装,用户只需调用一个方法执行,那

java/android 设计模式学习笔记(14)---外观模式

这篇博客来介绍外观模式(Facade Pattern),外观模式也称为门面模式,它在开发过程中运用频率非常高,尤其是第三方 SDK 基本很大概率都会使用外观模式.通过一个外观类使得整个子系统只有一个统一的高层的接口,这样能够降低用户的使用成本,也对用户屏蔽了很多实现细节.当然,在我们的开发过程中,外观模式也是我们封装 API 的常用手段,例如网络模块.ImageLoader 模块等.其实我们在开发过程中可能已经使用过很多次外观模式,只是没有从理论层面去了解它. 转载请注明出处:http://bl

java/android 设计模式学习笔记(10)---建造者模式

这篇博客我们来介绍一下建造者模式(Builder Pattern),建造者模式又被称为生成器模式,是创造性模式之一,与工厂方法模式和抽象工厂模式不同,后两者的目的是为了实现多态性,而 Builder 模式的目的则是为了将对象的构建与展示分离.Builder 模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细地控制对象的构造流程.一个复杂的对象有大量的组成部分,比如汽车它有车轮.方向盘.发动机.以及各种各样的小零件,要将这些部件装配成一辆汽车,这个装配过

java/android 设计模式学习笔记(13)---享元模式

这篇我们来介绍一下享元模式(Flyweight Pattern),Flyweight 代表轻量级的意思,享元模式是对象池的一种实现.享元模式用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,缓存可共享的对象,来达到对象共享和避免创建过多对象的效果,这样一来就可以提升性能,避免内存移除和频繁 GC 等. 享元模式的一个经典使用案例是文本系统中图形显示所用的数据结构,一个文本系统能够显示的字符种类就是那么几十上百个,那么就定义这么些基础字符对象,存储每个字符的显示外形和其他的格式化数据

java/android 设计模式学习笔记(9)---代理模式

这篇博客我们来介绍一下代理模式(Proxy Pattern),代理模式也成为委托模式,是一个非常重要的设计模式,不少设计模式也都会有代理模式的影子.代理在我们日常生活中也很常见,比如上网时连接的代理服务器地址,更比如我们平时租房子,将找房子的过程代理给中介等等,都是代理模式在日常生活中的使用例子. 代理模式中的代理对象能够连接任何事物:一个网络连接,一个占用很多内存的大对象,一个文件,或者是一些复制起来代价很高甚至根本不可能复制的一些资源.总之,代理是一个由客户端调用去访问幕后真正服务的包装对象

java/android 设计模式学习笔记(3)---工厂方法模式

这篇来介绍一下工厂方法模式(Factory Method Pattern),在实际开发过程中我们都习惯于直接使用 new 关键字用来创建一个对象,可是有时候对象的创造需要一系列的步骤:你可能需要计算或取得对象的初始设置:选择生成哪个子对象实例:或在生成你需要的对象之前必须先生成一些辅助功能的对象,这个时候就需要了解该对象创建的细节,也就是说使用的地方与该对象的实现耦合在了一起,不利于扩展,为了解决这个问题就需要用到我们的工厂方法模式,它适合那些创建复杂的对象的场景,工厂方法模式也是一个使用频率很

java/android 设计模式学习笔记(5)---对象池模式

这次要介绍一下对象池模式(Object Pool Pattern),这个模式为常见 23 种设计模式之外的设计模式,介绍的初衷主要是在平时的 android 开发中经常会看到,比如 ThreadPool 和 MessagePool 等. 在 java 中,所有对象的内存由虚拟机管理,所以在某些情况下,需要频繁创建一些生命周期很短使用完之后就可以立即销毁,但是数量很大的对象集合,那么此时 GC 的次数必然会增加,这时候为了减小系统 GC 的压力,对象池模式就很适用了.对象池模式也是创建型模式之一,

java/android 设计模式学习笔记(4)---抽象工厂模式

再来介绍一下抽象工厂模式(Abstact Factory Pattern),也是创建型模式之一,上篇博客主要介绍了工厂方法模式.抽象工厂模式和工厂方法模式稍有区别.工厂方法模式中工厂类生产出来的产品都是具体的,也就是说每个工厂都会生产某一种具体的产品,但是如果工厂类中所生产出来的产品是多种多样的,工厂方法模式也就不再适用了,就要使用抽象工厂模式了. 抽象工厂模式的起源或者最早的应用,是对不同操作系统的图形化解决方案,比如在不同操作系统中的按钮和文字框的不同处理,展示效果也不一样,对于每一个操作系