Java 设计模式系列(九)组合模式

Java 设计模式系列(九)组合模式

将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使得用户对单个对象的使用具有一致性。

一、组合模式结构

图9-1 组合模式结构

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

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

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

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

组合模式参考实现

(1)先看看组件对象的定义,示例代码如下:

/**
 * 抽象的组件对象,为组合中的对象声明接口,实现接口的缺省行为
 */
public abstract class Component {

    /** 示意方法,子组件对象可能有的功能方法 */
    public abstract void someOperation();

    /** 向组合对象中加入组件对象 */
    public void addChild(Component child) {
        // 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能
        throw new UnsupportedOperationException("对象不支持这个功能");
    }

    /** 从组合对象中移出某个组件对象 */
    public void removeChild(Component child) {
        // 缺省的实现,抛出例外,因为叶子对象没有这个功能,或者子组件没有实现这个功能
        throw new UnsupportedOperationException("对象不支持这个功能");
    }

    /** 返回某个索引对应的组件对象 */
    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);
    }
}

二、父组件引用

图9-2 父组件引用结构

(1) Component

public abstract class Component {
    /**
     * 记录父组件对象
     */
    private Component parent = null;

    /**
     * 获取一个组件的父组件对象
     * @return 一个组件的父组件对象
     */
    public Component getParent() {
        return parent;
    }
    /**
     * 设置一个组件的父组件对象
     * @param parent 一个组件的父组件对象
     */
    public void setParent(Component parent) {
        this.parent = parent;
    }
    /**
     * 返回某个组件的子组件对象
     * @return 某个组件的子组件对象
     */
    public List<Component> getChildren() {
        throw new UnsupportedOperationException("对象不支持这个功能");
    }

    /*-------------------以下是原有的定义----------------------*/

    /**
     * 输出组件自身的名称
     */
    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) Leaf

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

public class Composite extends Component{

    public void addChild(Component child) {
        //延迟初始化
        if (childComponents == null) {
            childComponents = new ArrayList<Component>();
        }
        childComponents.add(child);

        //添加对父组件的引用
        child.setParent(this);
    }

    public void removeChild(Component child) {
        if (childComponents != null) {
            //查找到要删除的组件在集合中的索引位置
            int idx = childComponents.indexOf(child);
            if (idx != -1) {
                //先把被删除的商品类别对象的父商品类别,设置成为被删除的商品类别的子类别的父商品类别
                for(Component c : child.getChildren()){
                    //删除的组件对象是本实例的一个子组件对象
                    c.setParent(this);
                    //把被删除的商品类别对象的子组件对象添加到当前实例中
                    childComponents.add(c);
                }

                //真的删除
                childComponents.remove(idx);
            }
        }
    }

    public List<Component> getChildren() {
        return childComponents;
    }

    /*-------------------以下是原有的实现,没有变化----------------------*/

    /**
     * 用来存储组合对象中包含的子组件对象
     */
    private List<Component> childComponents = null;

    /**
     * 组合对象的名字
     */
    private String name = "";

    /**
     * 构造方法,传入组合对象的名字
     * @param name 组合对象的名字
     */
    public Composite(String name){
        this.name = name;
    }

    /**
     * 输出组合对象自身的结构
     * @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);
            }
        }
    }
}

三、总结

(1) 组合模式的本质

组合模式的本质: 统一叶子对象和组合对象。

组合模式通过把叶子对象当成特殊的组合对象看待,从而对叶子对象和组合对象一视同仁,统统当成了Component对象,有机的统一了叶子对象和组合对象。

正是因为统一了叶子对象和组合对象,在将对象构建成树形结构的时候,才不需要做区分,反正是组件对象里面包含其它的组件对象,如此递归下去;也才使得对于树形结构的操作变得简单,不管对象类型,统一操作。

(2) 何时选用组合模式

  • 如果你想表示对象的部分-整体层次结构,可以选用组合模式,把整体和部分的操作统一起来,使得层次结构实现更简单,从外部来使用这个层次结构也简单。

  • 如果你希望统一的使用组合结构中的所有对象,可以选用组合模式,这正是组合模式提供的主要功能。

(3) 组合模式的优缺点

  • 定义了包含基本对象和组合对象的类层次结构

    在组合模式中,基本对象可以被组合成更复杂的组合对象,而组合对象又可以组合成更复杂的组合对象,可以不断地递归组合下去,从而构成一个统一的组合对象的类层次结构

  • 统一了组合对象和叶子对象

    在组合模式中,可以把叶子对象当作特殊的组合对象看待,为它们定义统一的父类,从而把组合对象和叶子对象的行为统一起来

  • 简化了客户端调用

    组合模式通过统一组合对象和叶子对象,使得客户端在使用它们的时候,就不需要再去区分它们,客户不关心使用的到底是什么类型的对象,这就大大简化了客户端的使用

  • 更容易扩展

    由于客户端是统一的面对Component来操作,因此,新定义的Composite或Leaf子类能够很容易的与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变

  • 很难限制组合中的组件类型

    容易增加新的组件也会带来一些问题,比如很难限制组合中的组件类型。这在需要检测组件类型的时候,使得我们不能依靠编译期的类型约束来完成,必须在运行期间动态检测。


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/9010613.html