设计模式解密(13)- 组合模式
1、简介
定义:允许你将对象组合成树形结构来表现"整体-部分"层次结构。 组合能让客户以一致的方法处理个别对象以及组合对象。
主要解决:它在我们树型结构的问题中,模糊了简单元素和复杂元素的概念,客户程序可以向处理简单元素一样来处理复杂元素,从而使得客户程序与复杂元素的内部结构解耦。
英文:Composite
类型:结构型
2、类图及组成
(引)类图:
组成:
● Component(抽象构件):接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的缺省声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
● Composite(容器构件):容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
● Leaf(叶子构件):叶子节点对象,定义和实现叶子对象的行为
,不再包含其它的子节点对象。
PS: 组合模式的关键是定义了一个抽象构件类,它既可以代表叶子,又可以代表容器,而客户端针对该抽象构件类进行编程,无须知道它到底表示的是叶子还是容器,可以对其进行统一处理。同时容器对象与抽象构件类之间还建立一个聚合关联关系,在容器对象中既可以包含叶子,也可以包含容器,以此实现递归组合,形成一个树形结构。
代码结构:
/** * 抽象构件角色: 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为 */ public abstract class Component { /** * 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象 */ public void add(Component child) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 从组合对象中移出某个组件对象 * @param child 被移出的组件对象 */ public void remove(Component child) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 返回某个索引对应的组件对象 * @param index 需要获取的组件对象的索引,索引从0开始 * @return 索引对应的组件对象 */ public Component getChild(int index) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 示意方法,子组件对象可能有的功能方法 */ public abstract void operation(); } /** * 容器构件:组合对象,通常需要存储子对象,定义有子部件的部件行为,并实现在Component里面定义的与子部件有关的操作 */ public class Composite extends Component { /** * 用来存储组合对象中包含的子组件对象 */ private List<Component> childComponents = null; public void add(Component child) { //延迟初始化 if (childComponents == null) { childComponents = new ArrayList<Component>(); } childComponents.add(child); } public void remove(Component child) { if (childComponents != null) { childComponents.remove(child); } } public Component getChild(int index) { if (childComponents != null){ if(index>=0 && index<childComponents.size()){ return childComponents.get(index); } } return null; } /** * 示意方法,通常在里面需要实现递归的调用 */ public void operation() { if (childComponents != null){ for(Component c : childComponents){ //递归的进行子组件相应方法的调用 c.operation(); } } } } /** * 叶子构件:叶子对象,叶子对象不再包含其它子对象 */ public class Leaf extends Component { /** * 示意方法,叶子对象可能有自己的功能方法 */ public void operation() { // do something } }
3、实例引入
我们用组合模式实现简单的组织架构:
+ 吊炸天股份有限公司
+ 北京分公司
- 研发部
- 市场部
- 省略...
+ 上海分公司
- 市场部
- 销售部
- 省略...
package com.designpattern.Composite; /** * 抽象构件角色 * @author Json<<json1990@foxmail.com>> */ public abstract class Component { /** * 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象 */ public void add(Component child) { throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 从组合对象中移出某个组件对象 * @param child 被移出的组件对象 */ public void remove(Component child) { throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 返回某个索引对应的组件对象 * @param index 需要获取的组件对象的索引,索引从0开始 * @return 索引对应的组件对象 */ public Component getChild(int index) { throw new UnsupportedOperationException("对象不支持这个功能"); } /** * 输出组件自身的名称 */ public abstract void outputSelf(String str); }
package com.designpattern.Composite; import java.util.ArrayList; import java.util.List; /** * 容器构件 * @author Json<<json1990@foxmail.com>> */ public class Composite extends Component { /** * 用来存储组合对象中包含的子组件对象 */ private List<Component> childComponents = null; /** * 组合对象的名字 */ private String name = ""; /** * 构造方法,传入组合对象的名字 * @param name 组合对象的名字 */ public Composite(String name){ this.name = name; } /** * 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象 */ public void add(Component child) { //延迟初始化 if (childComponents == null) { childComponents = new ArrayList<Component>(); } childComponents.add(child); } /** * 从组合对象中移出某个组件对象 * @param child 被移出的组件对象 */ public void remove(Component child) { if (childComponents != null) { childComponents.remove(child); } } /** * 输出组合对象自身的结构 * @param str 前缀,主要是按照层级拼接的空格,实现向后缩进 */ @Override public void outputSelf(String str) { //先把自己输出去 System.out.println(str+"+ "+this.name); //如果还包含有子组件,那么就输出这些子组件对象 if(this.childComponents != null){ //添加若干空格,表示向后缩进若干空格 str+=" "; //输出当前对象的子对象了 for(Component c : childComponents){ //递归输出每个子对象 c.outputSelf(str); } } } }
package com.designpattern.Composite; /** * 叶子 * @author Json<<json1990@foxmail.com>> */ public class Leaf extends Component { /** * 叶子对象的名字 */ private String name = ""; /** * 构造方法,传入叶子对象的名字 * @param name 叶子对象的名字 */ public Leaf(String name){ this.name = name; } /** * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @param str 前缀,主要是按照层级拼接的空格,实现向后缩进 */ public void outputSelf(String str) { System.out.println(str+"- "+name); } }
测试:
package com.designpattern.Composite; /** * 测试 * @author Json<<json1990@foxmail.com>> */ public class Client { public static void main(String[] args) { //定义所有的组合对象 Composite root = new Composite("吊炸天股份有限公司"); Composite c1 = new Composite("北京分公司"); Composite c2 = new Composite("上海分公司"); //定义所有的叶子对象 Leaf leaf1_1 = new Leaf("研发部"); Leaf leaf1_2 = new Leaf("市场部"); Leaf leaf1_3 = new Leaf("省略..."); Leaf leaf2_1 = new Leaf("市场部"); Leaf leaf2_2 = new Leaf("销售部"); Leaf leaf2_3 = new Leaf("省略..."); //按照树的结构来组合组合对象和叶子对象 root.add(c1); c1.add(leaf1_1); c1.add(leaf1_2); c1.add(leaf1_3); root.add(c2); c2.add(leaf2_1); c2.add(leaf2_2); c2.add(leaf2_3); //调用根对象的输出功能来输出整棵树 root.outputSelf(""); System.out.println("--------删除节点(上海分公司撤销了)↓↓↓----------"); root.remove(c2); root.outputSelf(""); } }
结果:
+ 吊炸天股份有限公司 + 北京分公司 - 研发部 - 市场部 - 省略... + 上海分公司 - 市场部 - 销售部 - 省略... --------删除节点(上海分公司撤销了)↓↓↓---------- + 吊炸天股份有限公司 + 北京分公司 - 研发部 - 市场部 - 省略...
PS:上面的例子很简单,只是实现了基本功能,在实际开发中可能遇到的情景,会比这个复杂,这里要明白组合模式的原理,化有形为无形,做到手中无剑,心中有剑;
4、优缺点
优点:
1、 组合模式可以清楚地定义分层次的复杂对象,表示对象的全部或部分层次,它让客户端忽略了层次的差异,方便对整个层次结构进行控制。
2、 客户端可以一致地使用一个组合结构或其中单个对象,不必关心处理的是单个对象还是整个组合结构,简化了客户端代码。
3、 在组合模式中增加新的容器构件和叶子构件都很方便,无须对现有类库进行任何修改,符合“开闭原则”。
4、 组合模式为树形结构的面向对象实现提供了一种灵活的解决方案,通过叶子对象和容器对象的递归组合,可以形成复杂的树形结构,但对树形结构的控制却非常简单。
缺点:
在增加新构件时很难对容器中的构件类型进行限制。有时候我们希望一个容器中只能有某些特定类型的对象,例如在某个文件夹中只能包含文本文件,使用组合模式时,不能依赖类型系统来施加这些约束,因为它们都来自于相同的抽象层,在这种情况下,必须通过在运行时进行类型检查来实现,这个实现过程较为复杂。
5、适用场景
1、 在对象具有部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也简单。
2、 系统中需要处理一个树形结构。
3、 在一个系统中能够分离出叶子对象和容器对象,而且它们的类型不固定,需要增加一些新的类型。
4、 想统一的使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。
实际应用:
XML解析
组织结构树处理
文件系统设计
......
6、总结
组合模式并不难理解,它主要解决的是单一对象和组合对象在使用方式上的一致性问题。如果对象具有明显的层次结构并且想要统一地使用它们,这就非常适合使用组合模式。在Web开发中,这种层次结构非常常见,很适合使用组合模式,达到对部分和整体使用的一致性。
PS:源码地址 https://github.com/JsonShare/DesignPattern/tree/master
PS:原文地址 http://www.cnblogs.com/JsonShare/p/7239560.html