模式解密(13)- 组合模式

Posted 与其临渊羡鱼,不如退而结网

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模式解密(13)- 组合模式相关的知识,希望对你有一定的参考价值。

1、简介

定义:允许你将对象组合成树形结构来表现"整体-部分"层次结构。 组合能让客户以一致的方法处理个别对象以及组合对象。

主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。

英文:Composite

类型:结构型

2、类图及组成

(引)类图:

组成:

  ● Component(抽象构件):接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的缺省声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。

  ● Composite(容器构件):容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。

  ● Leaf(叶子构件)叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象。

  PS: 组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。

 

代码结构:

/**
 * 抽象构件角色: 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
 */
public abstract class Component {
  /**
   * 向组合对象中加入组件对象
   * @param child 被加入组合对象中的组件对象
   */
  public void add(Component child) {
     // 缺省的实现,抛出例外,因为叶子对象没有这个功能,
     //或者子组件没有实现这个功能
     throw new UnsupportedOperationException("对象不支持这个功能");
  }
  
  /**
   * 从组合对象中移出某个组件对象
   * @param child 被移出的组件对象
   */
  public void remove(Component child) {
     // 缺省的实现,抛出例外,因为叶子对象没有这个功能,
     //或者子组件没有实现这个功能
     throw new UnsupportedOperationException("对象不支持这个功能");
  }
  
  /**
   * 返回某个索引对应的组件对象
   * @param index 需要获取的组件对象的索引,索引从0开始
   * @return 索引对应的组件对象
   */
  public Component getChild(int index) {
     // 缺省的实现,抛出例外,因为叶子对象没有这个功能,
     //或者子组件没有实现这个功能
     throw new UnsupportedOperationException("对象不支持这个功能");
  }
  
  /**
   * 示意方法,子组件对象可能有的功能方法
   */
  public abstract void operation();
}

/**
 * 容器构件:组合对象,通常需要存储子对象,定义有子部件的部件行为,并实现在Component里面定义的与子部件有关的操作
 */
public class Composite extends Component {
  /**
   * 用来存储组合对象中包含的子组件对象
   */
  private List<Component> childComponents = null;

  public void add(Component child) {
     //延迟初始化
     if (childComponents == null) {
         childComponents = new ArrayList<Component>();
     }
     childComponents.add(child);
  }
  
  public void remove(Component child) {
      if (childComponents != null) {
         childComponents.remove(child);
      }
  }
  
  public Component getChild(int index) {
     if (childComponents != null){
         if(index>=0 && index<childComponents.size()){
            return childComponents.get(index);
         }
     }
     return null;
  }
  
  /**
   * 示意方法,通常在里面需要实现递归的调用
   */
  public void operation() {     
     if (childComponents != null){
         for(Component c : childComponents){
            //递归的进行子组件相应方法的调用
            c.operation();
         }
     }
  }
}

/**
* 叶子构件:叶子对象,叶子对象不再包含其它子对象
*/
public class Leaf extends Component {
  /**
   * 示意方法,叶子对象可能有自己的功能方法
   */
  public void operation() {
     // do something
  }
}

3、实例引入

我们用组合模式实现简单的组织架构:

+ 吊炸天股份有限公司
  + 北京分公司
    - 研发部
    - 市场部
    - 省略...
  + 上海分公司
    - 市场部
    - 销售部
    - 省略...

package com.designpattern.Composite;

/**
 * 抽象构件角色
* @author Json<<json1990@foxmail.com>>
*/
public abstract class Component {
    /**
    * 向组合对象中加入组件对象
    * @param child 被加入组合对象中的组件对象
    */
    public void add(Component child) {
        throw new UnsupportedOperationException("对象不支持这个功能");
    }
    
    /**
    * 从组合对象中移出某个组件对象
    * @param child 被移出的组件对象
    */
    public void remove(Component child) {
        throw new UnsupportedOperationException("对象不支持这个功能");
    }
    
    /**
    * 返回某个索引对应的组件对象
    * @param index 需要获取的组件对象的索引,索引从0开始
    * @return 索引对应的组件对象
    */
    public Component getChild(int index) {
        throw new UnsupportedOperationException("对象不支持这个功能");
    }
    
    /**
    * 输出组件自身的名称
    */
    public abstract void outputSelf(String str);
}
package com.designpattern.Composite;

import java.util.ArrayList;
import java.util.List;

/**
 * 容器构件
 * @author Json<<json1990@foxmail.com>>
 */
public class Composite extends Component {
    /**
     * 用来存储组合对象中包含的子组件对象
     */
    private List<Component> childComponents = null;
    
    /**
     * 组合对象的名字
     */
    private String name = "";
    
    /**
     * 构造方法,传入组合对象的名字
     * @param name 组合对象的名字
     */
    public Composite(String name){
       this.name = name;
    }

    /**
    * 向组合对象中加入组件对象
    * @param child 被加入组合对象中的组件对象
    */
    public void add(Component child) {
       //延迟初始化
       if (childComponents == null) {
           childComponents = new ArrayList<Component>();
       }
       childComponents.add(child);
    }
    
    /**
    * 从组合对象中移出某个组件对象
    * @param child 被移出的组件对象
    */
    public void remove(Component child) {
        if (childComponents != null) {
            childComponents.remove(child);
         }     
    }
    
    /**
     * 输出组合对象自身的结构
     * @param str 前缀,主要是按照层级拼接的空格,实现向后缩进
     */
    @Override
    public void outputSelf(String str) {
       //先把自己输出去
       System.out.println(str+"+ "+this.name);
       //如果还包含有子组件,那么就输出这些子组件对象
       if(this.childComponents != null){
           //添加若干空格,表示向后缩进若干空格
           str+="    ";     
           //输出当前对象的子对象了
           for(Component c : childComponents){
              //递归输出每个子对象
              c.outputSelf(str);
           }
       }
    }
}
package com.designpattern.Composite;

/**
 * 叶子
 * @author Json<<json1990@foxmail.com>>
 */
public class Leaf extends Component {
    /**
     * 叶子对象的名字
     */
    private String name = "";
    
    /**
     * 构造方法,传入叶子对象的名字
     * @param name 叶子对象的名字
     */
    public Leaf(String name){
       this.name = name;
    }
    
    /**
     * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
     * @param str 前缀,主要是按照层级拼接的空格,实现向后缩进
     */
    public void outputSelf(String str) {
       System.out.println(str+"- "+name);
    }
}

 测试:

package com.designpattern.Composite;

/**
 * 测试
 * @author Json<<json1990@foxmail.com>>
 */
public class Client {
    public static void main(String[] args) {
        //定义所有的组合对象
        Composite root = new Composite("吊炸天股份有限公司");
        Composite c1 = new Composite("北京分公司");
        Composite c2 = new Composite("上海分公司");
        //定义所有的叶子对象
        Leaf leaf1_1 = new Leaf("研发部");
        Leaf leaf1_2 = new Leaf("市场部");
        Leaf leaf1_3 = new Leaf("省略...");
        
        Leaf leaf2_1 = new Leaf("市场部");
        Leaf leaf2_2 = new Leaf("销售部");
        Leaf leaf2_3 = new Leaf("省略...");

        //按照树的结构来组合组合对象和叶子对象
        root.add(c1);
        c1.add(leaf1_1);
        c1.add(leaf1_2);     
        c1.add(leaf1_3);     
        
        root.add(c2);      
        c2.add(leaf2_1);
        c2.add(leaf2_2);
        c2.add(leaf2_3);

        //调用根对象的输出功能来输出整棵树
        root.outputSelf("");
        
        System.out.println("--------删除节点(上海分公司撤销了)↓↓↓----------");
        root.remove(c2);
        root.outputSelf("");
    }
}

 结果:

+ 吊炸天股份有限公司
    + 北京分公司
        - 研发部
        - 市场部
        - 省略...
    + 上海分公司
        - 市场部
        - 销售部
        - 省略...
--------删除节点(上海分公司撤销了)↓↓↓----------
+ 吊炸天股份有限公司
    + 北京分公司
        - 研发部
        - 市场部
        - 省略...

PS:上面的例子很简单,只是实现了基本功能,在实际开发中可能遇到的情景,会比这个复杂,这里要明白组合模式的原理,化有形为无形,做到手中无剑,心中有剑;

4、优缺点

优点:

  1、 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。

  2、 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。

  3、 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。

  4、 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。

缺点:

  在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。

5、适用场景

  1、 在对象具有部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也简单。

  2、 系统中需要处理一个树形结构。

  3、 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。

  4、 想统一的使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。

  实际应用:
      XML解析
      组织结构树处理
      文件系统设计
      ......

6、总结

组合模式并不难理解,它主要解决的是单一对象和组合对象在使用方式上的一致性问题。如果对象具有明显的层次结构并且想要统一地使用它们,这就非常适合使用组合模式。在Web开发中,这种层次结构非常常见,很适合使用组合模式,达到对部分和整体使用的一致性。

 

PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master 

   

PS:原文地址 http://www.cnblogs.com/JsonShare/p/7239560.html

     

以上是关于模式解密(13)- 组合模式的主要内容,如果未能解决你的问题,请参考以下文章

设计模式解密(23) - 总结篇

大战设计模式13—— 组合模式

设计模式解密 - 建造者模式(生成器模式)

GO设计模式13组合模式

设计模式解密(20)- 职责链模式

初学设计模式之组合模式