首页 > 代码库 > 设计模式 -- 组合模式(Composite)

设计模式 -- 组合模式(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         //如果还包含子组件,那么就输出这些子组件对象
103         if(null != childComponents){
104             //添加一个空格,表示向后缩进一个空格
105             preStr += " ";
106             //输出当前对象的子对象
107             for(Component c : childComponents){
108                 //递归输出每个子对象
109                 c.printStruct(preStr);
110             }
111         }
112     }
113     
114     /**
115      * 向组合对象中加入组件对象
116      * @param child 被加入组合对象中组件对象
117      */
118     @Override
119     public void addChild(Component child) {
120         //延迟初始化
121         if(null == childComponents){
122             childComponents = new ArrayList<Component>();
123         }
124         childComponents.add(child);
125     }
126 }
127 
128 public class Client {
129 
130     public static void main(String[] args) {
131         //定义多个Composite对象
132         Component root = new Composite("服装");
133         Component c1 = new Composite("男装");
134         Component c2 = new Composite("女装");
135         
136         //定义多个叶子对象
137         Component leaf1 = new Leaf("衬衣");
138         Component leaf2 = new Leaf("夹克");
139         Component leaf3 = new Leaf("裙装");
140         Component leaf4 = new Leaf("套装");
141         
142         //组合成为树形的对象结构
143         root.addChild(c1);
144         root.addChild(c2);
145         c1.addChild(leaf1);
146         c1.addChild(leaf2);
147         c2.addChild(leaf3);
148         c2.addChild(leaf4);
149         
150         //操作Component对象
151         root.printStruct("");
152     }
153 }
154 
155 运行结果:
156 +服装
157  +男装
158   -衬衣
159   -夹克
160  +女装
161   -裙装
162   -套装
View Code

3、理解组合模式                                                                        

3.1、认识组合模式

1、组合模式的目的

  目的:让客户端不再区分操作的是组合对象还是叶子对象,而是以一个统一的方式来操

  实现组合模式的关键之处:设计一个抽象的组件类,让它可以代表组合对象和叶子对象。这样的话,客户端就不用区分到底操作的是组合对象还是叶子对象了,只需要把它们全部当作组件对象进行统一的操作就可以了。

2、对象树

  通常,组合模式会组合出树形结构来,组成这个树形结构所使用的多个组件对象,就自然的形成了对象树。

  这也意味着凡是可以使用对象树来描述或操作的功能,都可以考虑使用组合模式,比如读取XML文件,或是对语句进行语法解析等。

3、组合模式中的递归

  组合模式中的递归,指的是对象递归组合,不是常说的递归算法。

  而这里的组合模式中的递归,是对象本身的递归,是对象的组合方式,是从设计上来讲的,在设计上称为递归关联,是对象关联关系的一种。

3.2、安全性和透明性

  在组合模式中,把组件对象分成了两种:一种是可以包含子组件的Composite对象;另一种是不能包含其他组件对象的叶子对象。Composite对象就像是一个容器,可以包含其他的Composite对象或叶子对象,既然Composite是一个容器,那么就需要提供新增、修改操作来管理它,这就产生了一个很重要的问题:在组合模式的类层次结构中,到底在哪一些类里面定义这些管理子组件的操作,是应该在Component中声明这些操作呢?还是在Composite中声明这些操作?

在不同的实现中,需要进行安全性和透明性的权衡选择,关于安全性是指:从客户使用组合模式上看是否更安全。如果是安全的,那么久不会发生误操作的可能,能访问的方法都是被支持的功能;关于透明性是指:从客户使用组合模式上,是否需要区分到底是组合对象还是叶子对象。如果是透明的,那就不用再区分,对于客户而言,都是组件对象,具体的类型对于客户而言是透明的,是客户无须关心的。

(1)透明性的实现

  如果把管理子组件的操作定义在Component中,那么客户端只需要面对Component,而无须关系具体的组件类型,这种实现方式就是透明性的实现(前面示例代码就是这样的)。但是透明性的实现是以安全为代价的,因为在Component中定义的一些方法,对于叶子对象来说是没有意义的,比如:新增、删除子组件对象。

  组合模式的透明性实现,通常的方式是:在Component中声明管理子组件的操作,并在Component中为这些方法提供默认实现,如果子对象不支持的功能,默认的实现可以抛出一个异常,来表示不支持这个功能

(2)安全性的实现

  如果把管理子组件的操作定义在Composite中,那么客户在使用叶子对象的时候,就不会发生使用添加子组件或者删除子组件的操作了,因为压根就没有这样的功能,这种实现方式是安全的。但是这样的话,客户端在使用的时候,就必须区分到底使用的是Composite对象,还是叶子对象,不同对象的功能是不一样的,这样对于客户端而言就不是透明的了

下面代码是用安全性的方式实现的:

技术分享
  1 /**
  2  * 抽象的组件对象
  3  */
  4 public abstract class Component {
  5 
  6     /**
  7      * 输出组件自身的名称
  8      */
  9     public abstract void printStruct(String preStr);
 10 }
 11 
 12 /**
 13  * 叶子对象
 14  */
 15 public class Leaf extends Component {
 16 
 17     /**
 18      * 叶子对象的名称
 19      */
 20     private String name = "";
 21     
 22     /**
 23      * 构造方法
 24      * @param name 叶子对象的名称
 25      */
 26     public Leaf(String name){
 27         this.name = name;
 28     }
 29     
 30     /**
 31      * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名称
 32      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
 33      */
 34     @Override
 35     public void printStruct(String preStr) {
 36         System.out.println(preStr +"-" + name);
 37     }
 38 }
 39 
 40 import java.util.ArrayList;
 41 import java.util.List;
 42 
 43 /**
 44  * 组合对象,可以包含其他组合对象或者叶子对象
 45  */
 46 public class Composite extends Component {
 47     
 48     /**
 49      * 用来存储组合对象中的子组件
 50      */
 51     private List<Component> childComponents = null;
 52 
 53     /**
 54      * 组合对象的名称
 55      */
 56     private String name;
 57     
 58     /**
 59      * 构造方法
 60      * @param name 组合对象的名称
 61      */
 62     public Composite(String name){
 63         this.name = name;
 64     }
 65     
 66     /**
 67      * 输出组合对象自身的结构
 68      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
 69      */
 70     @Override
 71     public void printStruct(String preStr) {
 72         //先把自己输出去
 73         System.out.println(preStr + "+" + name);
 74         //如果还包含子组件,那么就输出这些子组件对象
 75         if(null != childComponents){
 76             //添加一个空格,表示向后缩进一个空格
 77             preStr += " ";
 78             //输出当前对象的子对象
 79             for(Component c : childComponents){
 80                 //递归输出每个子对象
 81                 c.printStruct(preStr);
 82             }
 83         }
 84     }
 85     
 86     /**
 87      * 向组合对象中加入组件对象
 88      * @param child 被加入组合对象中组件对象
 89      */
 90     public void addChild(Component child) {
 91         //延迟初始化
 92         if(null == childComponents){
 93             childComponents = new ArrayList<Component>();
 94         }
 95         childComponents.add(child);
 96     }
 97 }
 98 
 99 public class Client {
100 
101     public static void main(String[] args) {
102         //定义多个Composite对象
103         Component root = new Composite("服装");
104         Component c1 = new Composite("男装");
105         Component c2 = new Composite("女装");
106         
107         //定义多个叶子对象
108         Component leaf1 = new Leaf("衬衣");
109         Component leaf2 = new Leaf("夹克");
110         Component leaf3 = new Leaf("裙装");
111         Component leaf4 = new Leaf("套装");
112         
113         //组合成为树形的对象结构
114         root.addChild(c1);
115         root.addChild(c2);
116         c1.addChild(leaf1);
117         c1.addChild(leaf2);
118         c2.addChild(leaf3);
119         c2.addChild(leaf4);
120         
121         //操作Component对象
122         root.printStruct("");
123     }
124 }
125 
126 运行结果:
127 +服装
128  +男装
129   -衬衣
130   -夹克
131  +女装
132   -裙装
133   -套装
View Code

(3)两种实现方式的选择

  对于组合模式而言,在安全性和透明性上,会更看重透明性,毕竟组合模式的功能就是要让用户对叶子对象和组合对象的使用具有一致性。因此在使用组合模式的时候,建议多采用透明性的实现方式。

3.3、父组件引用

  在前面的示例中,都是在父组件对象中,保存有子组件的引用,也就是说都是从父到子的引用。下面会讨论从子组件对象到父组件对象的引用,要实现这样的功能,只需要在保持从父组件到子组件引用的基础上,再增加保持从子组件到父组件的引用,这样在删除一个组件对象或是调整一个组件对象的时候,可以通过调整父组件的引用来实现。

  通常会在Component中定义对父组件的引用,组合对象和叶子对象都可以继承这个引用。那么在什么时候维护这个引用呢? ---》 (在组合对象添加子组件对象的时候,为子组件对象设置父组件的引用,在组合对象删除一个子组件对象的时候,再重新设置相关子组件的父组件引用。把这些实现到Composite中,这样所有的子类都可以继承到这些方法,从而维护子组件到父组件的引用。)

下面通过示例代码进行说明:

技术分享
  1 import java.util.List;
  2 
  3 /**
  4  * 抽象的组件对象
  5  */
  6 public abstract class Component {
  7     
  8     /**
  9      * 记录父组件对象
 10      */
 11     private Component parent = null;
 12 
 13     /**
 14      * 获取一个组件的父组件对象
 15      * @return 一个组件的父组件对象
 16      */
 17     public Component getParent() {
 18         return parent;
 19     }
 20 
 21     /**
 22      * 设置一个组件的父组件对象
 23      * @param parent 一个组件的父组件对象
 24      */
 25     public void setParent(Component parent) {
 26         this.parent = parent;
 27     }
 28     
 29     /**
 30      * 返回某个组件的子组件对象
 31      * @return 某个组件的子组件对象
 32      */
 33     public List<Component> getChildren(){
 34         throw new UnsupportedOperationException("对象不支持此方法");
 35     }
 36     
 37     /*****以下是原有的定义*******/
 38     /**
 39      * 输出组件自身的名称
 40      */
 41     public abstract void printStruct(String preStr);
 42     
 43     /**
 44      * 向组合对象中加入组件对象
 45      * @param child 被加入组合对象中组件对象
 46      */
 47     public void addChild(Component child){
 48         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 49         throw new UnsupportedOperationException("对象不支持此方法");
 50     }
 51     
 52     /**
 53      * 从组合对象中移出某个组件对象
 54      * @param child 被移出的组件对象
 55      */
 56     public void removeChild(Component child){
 57         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 58         throw new UnsupportedOperationException("对象不支持此方法");
 59     }
 60     
 61     /**
 62      * 返回某个索引对应的组件对象
 63      * @param index 需要获取的组件对象的索引,索引从0开始
 64      * @return 索引对应的组件对象
 65      */
 66     public Component getChildren(int index){
 67         //缺省的实现,抛出例外,因为叶子对象没有这个功能或者子组件没有实现这个功能
 68         throw new UnsupportedOperationException("对象不支持此方法");
 69     }
 70 }
 71 
 72 import java.util.ArrayList;
 73 import java.util.List;
 74 
 75 /**
 76  * 组合对象,可以包含其他组合对象或者叶子对象
 77  */
 78 public class Composite extends Component {
 79     /**
 80      * 用来存储组合对象中的子组件
 81      */
 82     private List<Component> childComponents = null;
 83 
 84     /**
 85      * 组合对象的名称
 86      */
 87     private String name = "";
 88     
 89     /**
 90      * 构造方法
 91      * @param name 组合对象的名称
 92      */
 93     public Composite(String name){
 94         this.name = name;
 95     }
 96     
 97     /**
 98      * 输出组合对象自身的结构
 99      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
100      */
101     @Override
102     public void printStruct(String preStr) {
103         //先把自己输出去
104         System.out.println(preStr + "+" + name);
105         //如果还包含子组件,那么就输出这些子组件对象
106         if(null != childComponents){
107             //添加一个空格,表示向后缩进一个空格
108             preStr += " ";
109             //输出当前对象的子对象
110             for(Component c : childComponents){
111                 //递归输出每个子对象
112                 c.printStruct(preStr);
113             }
114         }
115     }
116     
117     /**
118      * 向组合对象中加入组件对象
119      * @param child 被加入组合对象中组件对象
120      */
121     @Override
122     public void addChild(Component child) {
123         //延迟初始化
124         if(null == childComponents){
125             childComponents = new ArrayList<Component>();
126         }
127         childComponents.add(child);
128         
129         //添加对父组件的引用
130         child.setParent(this);
131     }
132     
133     /**
134      * 删除组合对象中的子对象
135      */
136     @Override
137     public void removeChild(Component child) {
138         if(null != childComponents){
139             //查找到要删除的组件在集合中的索引位置
140             int index = childComponents.indexOf(child);
141             if(index != -1){
142                 //先把被删除的商品类别对象的父商品类别 设置成为被删除的商品类别的子类别的父商品类别
143                 for(Component c : child.getChildren()){
144                     //删除的组件对象是本实例的一个子组件对象
145                     c.setParent(this);
146                     //把被删除的商品类别对象的子组件对象添加到当前实例中
147                     childComponents.add(c);
148                 }
149                 
150                 //真正的删除
151                 childComponents.remove(index);
152             }
153         }
154     }
155     
156     @Override
157     public List<Component> getChildren() {
158         return childComponents;
159     }
160 }
161 
162 /**
163  * 叶子对象
164  */
165 public class Leaf extends Component {
166 
167     /**
168      * 叶子对象的名称
169      */
170     private String name = "";
171     
172     /**
173      * 构造方法
174      * @param name 叶子对象的名称
175      */
176     public Leaf(String name){
177         this.name = name;
178     }
179     
180     /**
181      * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名称
182      * @param preStr 前缀,主要是按照层次拼接的空格,实现向后缩进
183      */
184     @Override
185     public void printStruct(String preStr) {
186         System.out.println(preStr +"-" + name);
187     }
188 }
189 
190 public class Client {
191     public static void main(String[] args) {
192         //定义多个Composite对象
193         Component root = new Composite("服装");
194         Component c1 = new Composite("男装");
195         Component c2 = new Composite("女装");
196         
197         //定义多个叶子对象
198         Component leaf1 = new Leaf("衬衣");
199         Component leaf2 = new Leaf("夹克");
200         Component leaf3 = new Leaf("裙装");
201         Component leaf4 = new Leaf("套装");
202         
203         //组合成为树形的对象结构
204         root.addChild(c1);
205         root.addChild(c2);
206         c1.addChild(leaf1);
207         c1.addChild(leaf2);
208         c2.addChild(leaf3);
209         c2.addChild(leaf4);
210         
211         //操作Component对象
212         root.printStruct("");
213         System.out.println("-------------------------");
214         
215         //删除一个节点
216         root.removeChild(c1);
217         root.printStruct("");
218     }
219 }
220 
221 运行结果:
222 +服装
223  +男装
224   -衬衣
225   -夹克
226  +女装
227   -裙装
228   -套装
229 -------------------------
230 +服装
231  +女装
232   -裙装
233   -套装
234  -衬衣
235  -夹克
View Code

3.4、组合模式的优缺点

优点:

  1. 统一了组合对象和叶子对象,在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。
  2. 简化了客户端调用,组合模式统一了组合对象和叶子对象,客户端在调用的时候不需要区分它们

缺点:

  1. 很难限制组合中的组合类型。

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

4、思考组合模式                                                                                     

4.1、组合模式的本质:

  统一叶子对象和组合对象。组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,全部当成了Component对象,有机地统一了叶子对象和组合对象。

4.2、何时选用组合模式:

  如果想表示对象的部分--整体层次结构,可以选用组合模式,把整体和部分的操作统一起来。

  如果希望统一地使用组合结构中的所有对象,可以选用组合模式。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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