用来解决上述问题的一个合理的解决方案就是组合模式。那么什么是组合模式呢?
(1)组合模式定义
(2)应用组合模式来解决的思路
仔细分析上面不用模式的例子中,要区分组合对象和叶子对象的根本原因,就在于没有把组合对象和叶子对象统一起来,也就是说,组合对象类型和叶子对象类型是完全不同的类型,这导致了操作的时候必须区分它们。
组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是在操作叶子对象。
组合模式的关键就在于这个抽象类,这个抽象类既可以代表叶子对象,也可以代表组合对象,这样用户在操作的时候,对单个对象和组合对象的使用就具有了一致性。
组合模式的结构如图15.1所示:
图15.1 组合模式结构示意图
Component:
抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。
Leaf:
叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象。
Composite:
组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作。
Client:
客户端,通过组件接口来操作组合结构里面的组件对象。
一种典型的Composite对象结构通常是如图15.2所示的树形结构,一个Composite对象可以包含多个叶子多象和其它的Composite对象,虽然15.2的图看起来好像有些对称,但是那只是为了让图看起来美观一点,并不是说Composite组合的对象结构就是这样对称的,这点要提前说明一下。
图15.2 典型的Composite对象结构
(1)先看看组件对象的定义,示例代码如下:
/** * 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为 */ public abstract class Component { /** * 示意方法,子组件对象可能有的功能方法 */ public abstract void someOperation(); /** * 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象 */ public void addChild(Component child) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException( "对象不支持这个功能"); } /** * 从组合对象中移出某个组件对象 * @param child 被移出的组件对象 */ public void removeChild(Component child) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException( "对象不支持这个功能"); } /** * 返回某个索引对应的组件对象 * @param index 需要获取的组件对象的索引,索引从0开始 * @return 索引对应的组件对象 */ public Component getChildren(int index) { // 缺省的实现,抛出例外,因为叶子对象没有这个功能, //或者子组件没有实现这个功能 throw new UnsupportedOperationException( "对象不支持这个功能"); } } |
(2)接下来看看Composite对象的定义,示例代码如下:
/** * 组合对象,通常需要存储子对象,定义有子部件的部件行为, * 并实现在Component里面定义的与子部件有关的操作 */ public class Composite extends Component { /** * 用来存储组合对象中包含的子组件对象 */ private List /** * 示意方法,通常在里面需要实现递归的调用 */ public void someOperation() { if (childComponents != null){ for(Component c : childComponents){ //递归的进行子组件相应方法的调用 c.someOperation(); } } } public void addChild(Component child) { //延迟初始化 if (childComponents == null) { childComponents = new ArrayList } childComponents.add(child); } public void removeChild(Component child) { if (childComponents != null) { childComponents.remove(child); } } public Component getChildren(int index) { if (childComponents != null){ if(index>=0 && index return childComponents.get(index); } } return null; } } |
(3)该来看叶子对象的定义了,相对而言比较简单,示例代码如下:
/** * 叶子对象,叶子对象不再包含其它子对象 */ public class Leaf extends Component { /** * 示意方法,叶子对象可能有自己的功能方法 */ public void someOperation() { // do something } } |
(4)对于Client,就是使用Component接口来操作组合对象结构,由于使用方式千差万别,这里仅仅提供一个示范性质的使用,顺便当作测试代码使用,示例代码如下:
public class Client { public static void main(String[] args) { //定义多个Composite对象 Component root = new Composite(); Component c1 = new Composite(); Component c2 = new Composite(); //定义多个叶子对象 Component leaf1 = new Leaf(); Component leaf2 = new Leaf(); Component leaf3 = new Leaf();
//组合成为树形的对象结构 root.addChild(c1); root.addChild(c2); root.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3);
//操作Component对象 Component o = root.getChildren(1); System.out.println(o); } } |
理解了组合模式的定义、结构和示例代码过后,对组合模式应该有一定的掌握了,下面就来使用组合模式,来重写前面不用模式的示例,看看用组合模式来实现会是什么样子,跟不用模式有什么相同和不同之处。
为了整体理解和把握整个示例,先来看看示例的整体结构,如图15.3所示:
图15.3 使用组合模式实现示例的结构示意图
(1)首先就是要为组合对象和叶子对象添加一个抽象的父对象做为组件对象,在组件对象里面,定义一个输出组件本身名称的方法以实现要求的功能,示例代码如下:
/** * 抽象的组件对象 */ public abstract class Component { /** * 输出组件自身的名称 */ public abstract void printStruct(String preStr); /** * 向组合对象中加入组件对象 * @param child 被加入组合对象中的组件对象 */ public void addChild(Component child) { throw new UnsupportedOperationException( "对象不支持这个功能"); } /** * 从组合对象中移出某个组件对象 * @param child 被移出的组件对象 */ public void removeChild(Component child) { throw new UnsupportedOperationException( "对象不支持这个功能"); } /** * 返回某个索引对应的组件对象 * @param index 需要获取的组件对象的索引,索引从0开始 * @return 索引对应的组件对象 */ public Component getChildren(int index) { throw new UnsupportedOperationException( "对象不支持这个功能"); } } |
(2)先看叶子对象的实现,它变化比较少,只是让叶子对象继承了组件对象,其它的跟不用模式比较,没有什么变化,示例代码如下:
/** * 叶子对象 */ public class Leaf extends Component{ /** * 叶子对象的名字 */ private String name = ""; /** * 构造方法,传入叶子对象的名字 * @param name 叶子对象的名字 */ public Leaf(String name){ this.name = name; } /** * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字 * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */ public void printStruct(String preStr){ System.out.println(preStr+"-"+name); } } |
(3)接下来看看组合对象的实现,这个对象变化就比较多,大致有如下的改变:
具体的示例代码如下:
/** * 组合对象,可以包含其它组合对象或者叶子对象 */ public class Composite extends Component{ /** * 用来存储组合对象中包含的子组件对象 */ private List /** * 组合对象的名字 */ private String name = ""; /** * 构造方法,传入组合对象的名字 * @param name 组合对象的名字 */ public Composite(String name){ this.name = name; }
public void addChild(Component child) { //延迟初始化 if (childComponents == null) { childComponents = new ArrayList } childComponents.add(child); } /** * 输出组合对象自身的结构 * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进 */ public void printStruct(String preStr){ //先把自己输出去 System.out.println(preStr+"+"+this.name); //如果还包含有子组件,那么就输出这些子组件对象 if(this.childComponents!=null){ //然后添加一个空格,表示向后缩进一个空格 preStr+=" "; //输出当前对象的子对象了 for(Component c : childComponents){ //递归输出每个子对象 c.printStruct(preStr); } } } } |
(4)客户端也有变化,客户端不再需要区分组合对象和叶子对象了,统一都是使用组件对象,调用的方法也都要改变成组件对象定义的方法。示例代码如下:
public class Client { public static void main(String[] args) { //定义所有的组合对象 Component root = new Composite("服装"); Component c1 = new Composite("男装"); Component c2 = new Composite("女装");
//定义所有的叶子对象 Component leaf1 = new Leaf("衬衣"); Component leaf2 = new Leaf("夹克"); Component leaf3 = new Leaf("裙子"); Component leaf4 = new Leaf("套装");
//按照树的结构来组合组合对象和叶子对象 root.addChild(c1); root.addChild(c2); c1.addChild(leaf1); c1.addChild(leaf2); c2.addChild(leaf3); c2.addChild(leaf4); //调用根对象的输出功能来输出整棵树 root.printStruct(""); } } |
通过上面的示例,大家可以看出,通过使用组合模式,把一个“部分-整体”的层次结构表示成了对象树的结构,这样一来,客户端就无需再区分操作的是组合对象还是叶子对象了,对于客户端而言,操作的都是组件对象。
---------------------------------------------------------------------------
私塾在线学习网原创内容 跟着cc学设计系列 之 研磨设计模式
研磨设计讨论群【252780326】
原创内容,转载请注明出处【http://sishuok.com/forum/blogPost/list/0/5512.html】
---------------------------------------------------------------------------