设计模式 -- 组合模式(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 }

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   -套装

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   -套装

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   -套装

(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  -夹克

3.4、组合模式的优缺点

优点:

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

缺点:

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

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

4、思考组合模式                                                                                     

4.1、组合模式的本质:

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

4.2、何时选用组合模式:

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

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

 

时间: 2024-10-09 04:51:59

设计模式 -- 组合模式(Composite)的相关文章

设计模式 - 组合模式(composite pattern) 详解

组合模式(composite pattern) 详解 本文地址: http://blog.csdn.net/caroline_wendy 组合模式: 允许你将对象组合成树形结构来表现"整体/部分"层次结构. 组合能让客户以一致的方法处理个别对象以及组合对象. 建立组件类(Component), 组合类(composite)和叶子类(leaf)继承组件类, 客户类(client)直接调用最顶层的组合类(composite)即可. 具体方法: 1. 组件类(component), 包含组合

设计模式 - 组合模式(composite pattern) 迭代器(iterator) 详解

组合模式(composite pattern) 迭代器(iterator) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考组合模式(composite pattern): http://blog.csdn.net/caroline_wendy/article/details/36895627 在组合模式(composite pattern)添加迭代器功能, 遍历每一个组合(composite)的项. 具体方法: 1. 抽象组件类(abstract

说说设计模式~组合模式(Composite)

返回目录 何时能用到它? 组合模式又叫部分-整体模式,在树型结构中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦.对于今天这个例子来说,它可以很清楚的说明组合模式的用意,首先是一个Graphics对象,它表示是一绘图功能(树根),而circle,line和rectangle分别是简单的图形,它们内部不能再有其它图形了(相当于树叶),而picture是一个复杂图形,它由circle,line和rectangle组成(相当于树

设计模式组合模式(Composite)精华

23种子GOF设计模式一般分为三类:创建模式.结构模型.行为模式. 创建模式抽象的实例,他们帮助如何创建一个系统独立.这是一个这些对象和陈述的组合. 创建使用继承类的类架构更改实例.的对象类型模型的建立也将委托实例化一个对象. 创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些详细的类的信息封装起来.第二,它们隐藏了这些类的实例是怎样被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此.创建型模式在什么被创建.谁创建它,它是怎样被创建的,以及何时创建这些方

设计模式--组合模式Composite(结构型)

一.概念 组合模式允许你将对象组合成树形结构来表现"整体/部分"层次结构.组合能让客户以一致的方式处理个别对象以及对象组合. 二.UML图 1.Component(对象接口),定义了对象可以做的事情. 2.Leaf(叶子结点对象) 3.Composite(其他结点对象,包含其他结点或者叶子节点) 三.例子 快递公司一般都有层级结构 /** * 顺丰公司抽象类 * 定义了公司可以做哪些事情 * @author eason * */ public abstract class SFComp

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

定义: 对象组合成部分整体结构,单个对象和组合对象具有一致性. 看了下大概结构就是集团总公司和子公司那种层级结构. 角色介绍: Component :抽象根节点:其实相当去总公司,抽象子类共有的方法: Composite :相当于总公司的智能部门,也分管子公司,通过集合存储子节点对象,提供增删获取子节点对象的方法: leaf:子节点,相当于集团子公司,总公司具有的智能,子公司也具有,因此子节点具有总节点拥有的所有抽象方法以及提供给子类的方法. Client:通过抽象跟节点操作子节点的对象.

设计模式之组合模式(Composite)摘录

23种GOF设计模式一般分为三大类:创建型模式.结构型模式.行为模式. 创建型模式抽象了实例化过程,它们帮助一个系统独立于如何创建.组合和表示它的那些对象.一个类创建型模式使用继承改变被实例化的类,而一个对象创建型模式将实例化委托给另一个对象.创建型模式有两个不断出现的主旋律.第一,它们都将关于该系统使用哪些具体的类的信息封装起来.第二,它们隐藏了这些类的实例是如何被创建和放在一起的.整个系统关于这些对象所知道的是由抽象类所定义的接口.因此,创建型模式在什么被创建,谁创建它,它是怎样被创建的,以

设计模式(七)组合模式Composite(结构型)

设计模式(七)组合模式Composite(结构型) 1. 概述 在数据结构里面,树结构是很重要,我们可以把树的结构应用到设计模式里面. 例子1:就是多级树形菜单. 例子2:文件和文件夹目录 2.问题 我们可以使用简单的对象组合成复杂的对象,而这个复杂对象有可以组合成更大的对象.我们可以把简单这些对象定义成类,然后定义一些容器类来存储这些简单对象.客户端代码必须区别对象简单对象和容器对象,而实际上大多数情况下用户认为它们是一样的.对这些类区别使用,使得程序更加复杂.递归使用的时候跟麻烦,而我们如何

设计模式之组合模式---Composite Pattern

模式的定义 组合模式(Composite Pattern)定义如下: Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly. 将对象组合成树形结构以表示"部分-整体"的层次结构,使得用户对单个对象和组合对象的使用具有一致性.