23种设计模式之组合模式

15.2  解决方案

15.2.1  组合模式来解决

用来解决上述问题的一个合理的解决方案就是组合模式。那么什么是组合模式呢?

(1)组合模式定义

 

(2)应用组合模式来解决的思路

       仔细分析上面不用模式的例子中,要区分组合对象和叶子对象的根本原因,就在于没有把组合对象和叶子对象统一起来,也就是说,组合对象类型和叶子对象类型是完全不同的类型,这导致了操作的时候必须区分它们。

       组合模式通过引入一个抽象的组件对象,作为组合对象和叶子对象的父对象,这样就把组合对象和叶子对象统一起来了,用户使用的时候,始终是在操作组件对象,而不再去区分是在操作组合对象还是在操作叶子对象。

组合模式的关键就在于这个抽象类,这个抽象类既可以代表叶子对象,也可以代表组合对象,这样用户在操作的时候,对单个对象和组合对象的使用就具有了一致性。

15.2.2  模式结构和说明

组合模式的结构如图15.1所示:

 

图15.1  组合模式结构示意图

Component:

       抽象的组件对象,为组合中的对象声明接口,让客户端可以通过这个接口来访问和管理整个对象结构,可以在里面为定义的功能提供缺省的实现。

Leaf:

       叶子节点对象,定义和实现叶子对象的行为,不再包含其它的子节点对象。

Composite:

       组合对象,通常会存储子组件,定义包含子组件的那些组件的行为,并实现在组件接口中定义的与子组件有关的操作。

Client:

       客户端,通过组件接口来操作组合结构里面的组件对象。

 

       一种典型的Composite对象结构通常是如图15.2所示的树形结构,一个Composite对象可以包含多个叶子多象和其它的Composite对象,虽然15.2的图看起来好像有些对称,但是那只是为了让图看起来美观一点,并不是说Composite组合的对象结构就是这样对称的,这点要提前说明一下。

 

图15.2  典型的Composite对象结构

15.2.3  组合模式的示例代码

(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<Component> childComponents = null;

    /**

     * 示意方法,通常在里面需要实现递归的调用

     */

    public void someOperation() {     

       if (childComponents != null){

           for(Component c : childComponents){

              //递归的进行子组件相应方法的调用

              c.someOperation();

           }

       }

    }

    public void addChild(Component child) {

       //延迟初始化

       if (childComponents == null) {

           childComponents = new ArrayList<Component>();

       }

       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<childComponents.size()){

              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.2.4  使用组合模式重写示例

理解了组合模式的定义、结构和示例代码过后,对组合模式应该有一定的掌握了,下面就来使用组合模式,来重写前面不用模式的示例,看看用组合模式来实现会是什么样子,跟不用模式有什么相同和不同之处。

为了整体理解和把握整个示例,先来看看示例的整体结构,如图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)接下来看看组合对象的实现,这个对象变化就比较多,大致有如下的改变:

  • 新的Composite对象需要继承组件对象
  • 原来用来记录包含的其它组合对象的集合,和包含的其它叶子对象的集合,这两个集合被合并成为一个,就是统一的包含其它子组件对象的集合。使用组合模式来实现,不再需要区分到底是组合对象还是叶子对象了
  • 原来的addComposite和addLeaf的方法,可以不需要了,合并实现成组件对象中定义的方法addChild,当然需要现在的Composite来实现这个方法。使用组合模式来实现,不再需要区分到底是组合对象还是叶子对象了
  • 原来的printStruct方法的实现,完全要按照现在的方式来写,变化较大

具体的示例代码如下:

/**

 * 组合对象,可以包含其它组合对象或者叶子对象

 */

public class Composite extends Component{

    /**

     * 用来存储组合对象中包含的子组件对象

     */

    private List<Component> childComponents = null;

    /**

     * 组合对象的名字

     */

    private String name = "";

    /**

     * 构造方法,传入组合对象的名字

     * @param name 组合对象的名字

     */

    public Composite(String name){

       this.name = name;

    }

   

    public void addChild(Component child) {

       //延迟初始化

       if (childComponents == null) {

           childComponents = new ArrayList<Component>();

       }

       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("");

    }

}

       通过上面的示例,大家可以看出,通过使用组合模式,把一个“部分-整体”的层次结构表示成了对象树的结构,这样一来,客户端就无需再区分操作的是组合对象还是叶子对象了,对于客户端而言,操作的都是组件对象。

本电子书一共两个压缩文档,本文件为part2. 《研磨设计模式》完整覆盖GoF讲述的23个设计模式并加以细细研磨。初级内容从基本讲起,包括每个模式的定义、功能、思路、结构、基本实现、运行调用顺序、基本应用示例等,让读者能系统、完整、准确地掌握每个模式,培养正确的“设计观”;中高级内容则深入探讨如何理解这些模式,包括模式中蕴涵什么样的设计思想,模式的本质是什么,模式如何结合实际应用,模式的优缺点以及与其他模式的关系等,以期让读者尽量去理解和掌握每个设计模式的精髓所在。    《研磨设计模式》在内容上深入、技术上实用、和实际开发结合程度很高,书中大部分的示例程序都是从实际项目中简化而来,因此很多例子都可以直接拿到实际项目中使用。如果你想要深入透彻地理解和掌握设计模式,并期望能真正把设计模式应用到项目中去,那么这是你不可错过的一本好书。    《研磨设计模式》难度为初级到中级,适合与所有开发人员、设计人员或者即将成为开发人员的朋友。也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础    第2章 简单工厂    第3章 外观模式    第4章 适配器模式(Adapter)    第5章 单例模式(Singleton)    第6章 工厂方法模式(Factory Method)    第7章 抽象工厂模式(Abstract Factory)    第8章 生成器模式(Builder)    第9章 原型模式(Prototype)    第10章 中介者模式(Mediator)    第11章 代理模式(Proxy)    第12章 观察者模式(Observer)    第13章 命令模式(Command)    第14章 迭代器模式(Iterator)    第15章 组合模式(Composite)    第16章 模板方法模式(Template Method)    第17章 策略模式(Strategy)    第18章 状态模式(State)    第19章 备忘录模式(Memento)    第20章 享元模式(Flyweight)    第21章 解释器模式(Interpreter)    第22章 装饰模式(Decorator)    第23章 职责链模式(Chain of Responsibility)    第24章 桥接模式(Bridge)    第25章 访问者模式(Visitor)    附录A常见面向对象设计原则    附录BUML简介    参考文献
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值