设计模式 -- 组合模式(Composite)

Posted Hello

tags:

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

写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:

  1. 初识组合模式,包括:定义、结构、参考实现
  2. 体会组合模式,包括:场景问题、不用模式的解决方案、使用模式的解决方案
  3. 理解组合模式,包括:认识组合模式、安全性和透明性、父组件引用、环状引用、组合模式的优缺点
  4. 思考组合模式,包括:组合模式的本质、何时选用

参考内容:

1、《研磨设计模式》 一书,作者:陈臣、王斌

---------------------------------------------------------------------  

1、初始组合模式                                                                  

1.1、定义

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

1.2、结构和说明

  1. Component: 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
  2. Leaf: 叶子节点对象,定义和实现叶子对象的行为,不再包含其他的叶子对象。
  3. Composite: 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为。
  4. Client: 客户端,通过组件接口来操作组合结构里面的组件对象。

一种典型的Composite对象结构通常是如下图所示的树形结构:

1.3、参考实现

  1 (1)组件对象的定义
  2 /**
  3  * 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
  4  */
  5 public abstract class Component {
  6 
  7     /**
  8      * 示意方法,子组件对象可能有的功能方法
  9      */
 10     public abstract void someOperation();
 11     
 12     /**
 13      * 向组合对象中加入组件对象
 14      * @param child 被加入组合对象中组件对象
 15      */
 16     public void addChild(Component child){
 17         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 18         throw new UnsupportedOperationException("对象不支持此方法");
 19     }
 20     
 21     /**
 22      * 从组合对象中移出某个组件对象
 23      * @param child 被移出的组件对象
 24      */
 25     public void removeChild(Component child){
 26         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 27         throw new UnsupportedOperationException("对象不支持此方法");
 28     }
 29     
 30     /**
 31      * 返回某个索引对应的组件对象
 32      * @param index 需要获取的组件对象的索引,索引从0开始
 33      * @return 索引对应的组件对象
 34      */
 35     public Component getChildren(int index){
 36         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 37         throw new UnsupportedOperationException("对象不支持此方法");
 38     }
 39 }
 40 
 41 (2)Composite的定义
 42 import java.util.ArrayList;
 43 import java.util.List;
 44 
 45 /**
 46  * 组合对象,通常需要存储子对象
 47  */
 48 public class Composite extends Component {
 49     
 50     /**
 51      * 用来存储组合对象中包含的组件对象
 52      */
 53     private List<Component> childComponents = null;
 54 
 55     /**
 56      * 操作
 57      */
 58     @Override
 59     public void someOperation() {
 60         if(childComponents != null){
 61             for(Component c : childComponents){
 62                 //递归地进行子组件相应方法的调用
 63                 c.someOperation();
 64             }
 65         }
 66     }
 67     
 68     /**
 69      * 向组合对象中加入组件对象
 70      * @param child 被加入组合对象中组件对象
 71      */
 72     @Override
 73     public void addChild(Component child) {
 74         //延迟初始化
 75         if(null == childComponents){
 76             childComponents = new ArrayList<Component>();
 77         }
 78         childComponents.add(child);
 79     }
 80     
 81     /**
 82      * 从组合对象中移出某个组件对象
 83      * @param child 被移出的组件对象
 84      */
 85     @Override
 86     public void removeChild(Component child) {
 87         if(null != childComponents){
 88             childComponents.remove(child);
 89         }
 90     }
 91     
 92     /**
 93      * 返回某个索引对应的组件对象
 94      * @param index 需要获取的组件对象的索引,索引从0开始
 95      * @return 索引对应的组件对象
 96      */
 97     @Override
 98     public Component getChildren(int index) {
 99         if(null != childComponents){
100             if(index >= 0 && index < childComponents.size()){
101                 return childComponents.get(index);
102             }
103         }
104         return null;
105     }
106 }
107 
108 (3)叶子对象的定义
109 public class Leaf extends Component {
110 
111 
112     @Override
113     public void someOperation() {
114         //示例代码
115     }
116 }
117 
118 (4)客户端
119 public class Client {
120 
121     public static void main(String[] args) {
122         //定义多个Composite对象
123         Component root = new Composite();
124         Component c1 = new Composite();
125         Component c2 = new Composite();
126         
127         //定义多个叶子对象
128         Component leaf1 = new Leaf();
129         Component leaf2 = new Leaf();
130         Component leaf3 = new Leaf();
131         
132         //组合成为树形的对象结构
133         root.addChild(c1);
134         root.addChild(c2);
135         root.addChild(leaf1);
136         c1.addChild(leaf2);
137         c2.addChild(leaf3);
138         
139         //操作Component对象
140         Component o = root.getChildren(1);
141         System.out.println(o);
142     }
143 }
View Code

2、体会组合模式                                                                        

2.1、商品类别树

  考虑这样的实际应用:在实现跟商品有关的应用系统的时候,一个很常见的功能就是商品类别树的管理,比如有如下所示的商品类别树:

上图中是一个服装类的商品类别树,仔细观察上图可以知道以下几个的特点:

  1. 根节点,比如服装,它没有父节点,它可以包含其他的节点。
  2. 树枝节点,有一类节点可以包含其它的节点,称之为树枝节点,比如男装、女装。
  3. 叶子节点,有一类节点没有子节点,称之为叶子节点,比如衬衣、夹克、裙子、套装

如果现在需要管理服装商品类别树,要求能实现输出如上服装商品类型树的结构功能,应该如何实现呢?

2.2、不用模式的解决方案

  要管理商品类别树,就是要管理树的各个节点,现在树上的节点有三类: 根节点、树枝节点、叶子节点,再进一步分析发现,根节点和树枝节点时类似的,都是可以包含其它节点的节点,把它们称为容器节点。

  这样一来,商品类别树的节点就被分成了两种:一种是容器节点,另一种是叶子节点。容器节点可以包含其它的容器节点或者叶子节点。把他们分别实现成为对象,也就是容器对象和叶子对象,容器对象可以包含其它的容器对象或者叶子对象,换句话说,容器对象是一种组合对象。

不用模式解决的示例代码如下:

  1 /**
  2  * 叶子对象
  3  */
  4 public class Leaf {
  5     /**
  6      * 叶子对象的名称
  7      */
  8     private String name = "";
  9     
 10     /**
 11      * 构造方法
 12      * @param name 叶子对象的名称
 13      */
 14     public Leaf(String name){
 15         this.name = name;
 16     }
 17     
 18     /**
 19      * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名称
 20      * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
 21      */
 22     public void printStruct(String preStr){
 23         System.out.println(preStr+"-"+name);
 24     }
 25 }
 26 
 27 import java.util.ArrayList;
 28 import java.util.Collection;
 29 
 30 /**
 31  * 组合对象,可以包含其他组合对象或者叶子对象
 32  */
 33 public class Composite {
 34     /**
 35      * 用来记录包含的其他叶子对象
 36      */
 37     private Collection<Leaf> leafs = new ArrayList<Leaf>();
 38     /**
 39      * 用来记录包含的其他组合对象
 40      */
 41     private Collection<Composite> composites = new ArrayList<Composite>();
 42     /**
 43      * 组合对象的名称
 44      */
 45     private String name;
 46     
 47     /**
 48      * 构造方法
 49      * @param name 组合对象的名称
 50      */
 51     public Composite(String name){
 52         this.name = name;
 53     }
 54     
 55     /**
 56      * 向组合对象中添加被它包含的叶子对象
 57      * @param leaf 叶子对象
 58      */
 59     public void addLeaf(Leaf leaf){
 60         this.leafs.add(leaf);
 61     }
 62     
 63     /**
 64      * 向组合对象中添加被它包含的其它组合对象
 65      * @param c 其它组合对象
 66      */
 67     public void addComposite(Composite c){
 68         this.composites.add(c);
 69     }
 70     
 71     /**
 72      * 输出组合对象自身的结构
 73      * @param preStr 前缀,主要按照层级拼接空格,实现向后缩进
 74      */
 75     public void printStruct(String preStr){
 76         //先把组合对象自己输出去
 77         System.out.println(preStr+"+"+name);
 78         //添加一个空格,表示向后缩进一个空格
 79         preStr += " ";
 80         
 81         //输出当前组合对象包含的组合对象
 82         for(Composite c : composites){
 83             c.printStruct(preStr);
 84         }
 85         
 86         //输出当前组合对象包含的叶子对象
 87         for(Leaf leaf : leafs){
 88             leaf.printStruct(preStr);
 89         }        
 90     }
 91 }
 92 
 93 public class Client {
 94     public static void main(String[] args) {
 95         //定义所有的组合对象
 96         Composite root = new Composite("服装");
 97         Composite c1 = new Composite("男装");
 98         Composite c2 = new Composite("女装");
 99         
100         //定义所有的叶子对象
101         Leaf l1 = new Leaf("衬衣");
102         Leaf l2 = new Leaf("夹克");
103         Leaf l3 = new Leaf("裙子");
104         Leaf l4 = new Leaf("套装");
105         
106         //按照树的结构来组合组合对象和叶子对象
107         root.addComposite(c1);
108         root.addComposite(c2);
109         
110         c1.addLeaf(l1);
111         c1.addLeaf(l2);
112         c2.addLeaf(l3);
113         c2.addLeaf(l4);
114         
115         root.printStruct("");
116     }
117 }
118 
119 运行结果:
120 +服装
121  +男装
122   -衬衣
123   -夹克
124  +女装
125   -裙子
126   -套装
View Code

2.3、有何问题

  上面的实现,虽然能实现要求的功能,但是有一个很明显的问题:必须区分组合对象和叶子对象,并进行有区别的对待,比如在Composite和Client里面,都需要去区别对待这两种对象。

  区别对待组合对象和叶子对象,不仅让程序变得复杂,还对功能的扩展也带来不便。实际上,大多数情况下用户并不想要去区分它们,而是认为它们就是一样的,这样他们操作起来最简单。换句话说,对于这种具有整体与部分的关系,并能组合成树形结构的对象结构,如何才能够以一个统一的方式来进行操作呢?

2.4、使用组合模式来解决问题

使用模式的解决方案的类图:

  1. Component: 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
  2. Leaf: 叶子节点对象,定义和实现叶子对象的行为,不再包含其他的叶子对象。
  3. Composite: 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为。
  4. Client: 客户端,通过组件接口来操作组合结构里面的组件对象。

使用组合模式来解决问题的示例代码:

  1 /**
  2  * 抽象的组件对象
  3  */
  4 public abstract class Component {
  5 
  6     /**
  7      * 输出组件自身的名称
  8      */
  9     public abstract void printStruct(String preStr);
 10     
 11     /**
 12      * 向组合对象中加入组件对象
 13      * @param child 被加入组合对象中组件对象
 14      */
 15     public void addChild(Component child){
 16         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 17         throw new UnsupportedOperationException("对象不支持此方法");
 18     }
 19     
 20     /**
 21      * 从组合对象中移出某个组件对象
 22      * @param child 被移出的组件对象
 23      */
 24     public void removeChild(Component child){
 25         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 26         throw new UnsupportedOperationException("对象不支持此方法");
 27     }
 28     
 29     /**
 30      * 返回某个索引对应的组件对象
 31      * @param index 需要获取的组件对象的索引,索引从0开始
 32      * @return 索引对应的组件对象
 33      */
 34     public Component getChildren(int index){
 35         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 36         throw new UnsupportedOperationException("对象不支持此方法");
 37     }
 38 }
 39 
 40 /**
 41  * 叶子对象
 42  */
 43 public class Leaf extends Component {
 44 
 45     /**
 46      * 叶子对象的名称
 47      */
 48     private String name = "";
 49     
 50     /**
 51      * 构造方法
 52      * @param name 叶子对象的名称
 53      */
 54     public Leaf(String name){
 55         this.name = name;
 56     }
 57     
 58     /**
 59      * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名称
 60      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
 61      */
 62     @Override
 63     public void printStruct(String preStr) {
 64         System.out.println(preStr +"-" + name);
 65     }
 66 }
 67 
 68 import java.util.ArrayList;
 69 import java.util.List;
 70 
 71 /**
 72  * 组合对象,可以包含其他组合对象或者叶子对象
 73  */
 74 public class Composite extends Component {
 75     
 76     /**
 77      * 用来存储组合对象中的子组件
 78      */
 79     private List<Component> childComponents = null;
 80 
 81     /**
 82      * 组合对象的名称
 83      */
 84     private String name;
 85     
 86     /**
 87      * 构造方法
 88      * @param name 组合对象的名称
 89      */
 90     public Composite(String name){
 91         this.name = name;
 92     }
 93     
 94     /**
 95      * 输出组合对象自身的结构
 96      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
 97      */
 98     @Override
 99     public void printStruct(String preStr) {
100         //先把自己输出去
101         System.out.println(preStr + "+" + name);
102         //如果还包含子组件,那么就输出这些子组件对象
设计模式之Composite(组合)(转)

JAVA SCRIPT设计模式--结构型--设计模式之Composite组合模式

设计模式 -- 组合模式(Composite)

设计模式学习篇-Composite组合模式

设计模式:组合模式(Composite)

组合模式(Composite Pattern)