• 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子类能够很容易的与已有的结构一起工作,而客户端不需要为增添了新的组件类而改变

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

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


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

  • 相关阅读:
    KL散度(相对熵)和交叉熵的区别
    将模型从 PyTorch 导出到 ONNX 并使用 ONNX Runtime 运行
    Numpy Boolean Indexing Mask(Numpy 布尔索引掩码 )
    python PIL 图像处理库(Pillow)简介
    YOLO v3 网络结构和源码详解
    PyTorch 下使用 Tensorboard
    Python vars() 函数
    python 的 Tqdm 模块
    Pytorch 中的模式设置:model.eval() 和 model.train()
    Pytorch 中的 zero_grad 使用方法
  • 原文地址:https://www.cnblogs.com/binarylei/p/9010613.html
Copyright © 2020-2023  润新知