设计模式(十五)---组合模式

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

è¿éåå¾çæè¿°

特点

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

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

组合模式使用的场景:

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

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 个设计模式原则中依赖倒置原则,客户端不应该直接依赖于具体实现,而应该依赖于抽象,既然是面向接口编程,就应该把更多的焦点放在接口的设计上,于是这样就产生了透明的组合模式。

透明的组合模式

è¿éåå¾çæè¿°

和安全的组合模式差异就是在将 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 的相关方法,这么去实现当然也是可以的。

示例与源码

 组合模式在实际生活过程中的例子就数不胜数了,比如菜单、文件夹等等。我们这就以 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/

发布了205 篇原创文章 · 获赞 217 · 访问量 235万+

猜你喜欢

转载自blog.csdn.net/heng615975867/article/details/105028965