设计模式 -- 组合模式(Composite)
Posted Hello
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式 -- 组合模式(Composite)相关的知识,希望对你有一定的参考价值。
写在前面的话:读书破万卷,编码如有神
--------------------------------------------------------------------
主要内容包括:
- 初识组合模式,包括:定义、结构、参考实现
- 体会组合模式,包括:场景问题、不用模式的解决方案、使用模式的解决方案
- 理解组合模式,包括:认识组合模式、安全性和透明性、父组件引用、环状引用、组合模式的优缺点
- 思考组合模式,包括:组合模式的本质、何时选用
参考内容:
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 //如果还包含子组件,那么就输出这些子组件对象 设计模式之Composite(组合)(转)