写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:
- 初识组合模式,包括:定义、结构、参考实现
- 体会组合模式,包括:场景问题、不用模式的解决方案、使用模式的解决方案
- 理解组合模式,包括:认识组合模式、安全性和透明性、父组件引用、环状引用、组合模式的优缺点
- 思考组合模式,包括:组合模式的本质、何时选用
参考内容:
1、《研磨设计模式》 一书,作者:陈臣、王斌
---------------------------------------------------------------------
1、初始组合模式
1.1、定义
将对象组合成树型结构以表示"部分-整体"的层次结构。组合模式使得用户对单个对象和组合对象的使用具有一致性。
1.2、结构和说明
- Component: 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
- Leaf: 叶子节点对象,定义和实现叶子对象的行为,不再包含其他的叶子对象。
- Composite: 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为。
- 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、商品类别树
考虑这样的实际应用:在实现跟商品有关的应用系统的时候,一个很常见的功能就是商品类别树的管理,比如有如下所示的商品类别树:
上图中是一个服装类的商品类别树,仔细观察上图可以知道以下几个的特点:
- 根节点,比如服装,它没有父节点,它可以包含其他的节点。
- 树枝节点,有一类节点可以包含其它的节点,称之为树枝节点,比如男装、女装。
- 叶子节点,有一类节点没有子节点,称之为叶子节点,比如衬衣、夹克、裙子、套装
如果现在需要管理服装商品类别树,要求能实现输出如上服装商品类型树的结构功能,应该如何实现呢?
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、使用组合模式来解决问题
使用模式的解决方案的类图:
- Component: 抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
- Leaf: 叶子节点对象,定义和实现叶子对象的行为,不再包含其他的叶子对象。
- Composite: 组合对象,通常会存储子组件,定义包含子组件的那些组件的行为。
- 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、组合模式的优缺点
优点:
- 统一了组合对象和叶子对象,在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来。
- 简化了客户端调用,组合模式统一了组合对象和叶子对象,客户端在调用的时候不需要区分它们
缺点:
- 很难限制组合中的组合类型。
---------------------------------------------------------------------------------
4、思考组合模式
4.1、组合模式的本质:
统一叶子对象和组合对象。组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,全部当成了Component对象,有机地统一了叶子对象和组合对象。
4.2、何时选用组合模式:
如果想表示对象的部分--整体层次结构,可以选用组合模式,把整体和部分的操作统一起来。
如果希望统一地使用组合结构中的所有对象,可以选用组合模式。