zoukankan      html  css  js  c++  java
  • 「补课」进行时:设计模式(14)——组合模式

    1. 前文汇总

    「补课」进行时:设计模式系列

    2. 某东的菜单

    前段时间双十一,不知道各位的战果如何,反正我是屯了两盒口罩凑个数。

    电商平台为我们提供的方便快捷的搜索框入口,我想大多数人在使用的时候应该都会使用这个入口,但其实电商平台还为我们提供了另一个入口,就是它的分类体系,如下:

    我简单抽象一下:

    - 服装
        - 男装
            - 衬衣
            - 夹克
        - 女装
            - 裙子
            - 套装
    

    可以看到,这是一个树结构,在前端实现一个这种菜单树可以选用 ZTree 插件,做过前端的都知道。

    下面,用 Java 代码实现一下,输出一下上面的这个树状结构:

    观察这个树状结构,可以看到节点分为三种类型:

    • 根节点
    • 树枝节点
    • 叶子节点(没有子节点)

    根节点和树枝节点的构造是比较类似的,都是可以有子节点,这两个节点可以抽象成一个对象。

    首先定义一个叶子节点:

    public class Leaf {
        // 叶子对象的名字
        private String name;
        // 构造方法
        public Leaf(String name) {
            this.name = name;
        }
        // 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
        public void printStruct(String preStr) {
            System.out.println(preStr + " - " + name);
        }
    }
    

    接着定义一个组合对象:

    public class Composite {
        // 组合对象集合
        private Collection<Composite> childComposite = new ArrayList<>();
        // 叶子对象集合
        private Collection<Leaf> childLeaf = new ArrayList<>();
        // 组合对象名称
        private String name;
        // 构造函数
        public Composite(String name) {
            this.name = name;
        }
        // 向组合对象加入被它包含的其它组合对象
        public void addComposite(Composite c){
            this.childComposite.add(c);
        }
        // 向组合对象加入被它包含的叶子对象
        public void addLeaf(Leaf leaf){
            this.childLeaf.add(leaf);
        }
        // 输出自身结构
        public void printStruct(String preStr){
            System.out.println(preStr + " + " + this.name);
            preStr+=" ";
            for(Leaf leaf : childLeaf){
                leaf.printStruct(preStr);
            }
            for(Composite c : childComposite){
                c.printStruct(preStr);
            }
        }
    }
    

    来一个测试类:

    public class Test {
        public static void main(String[] args) {
            //定义所有的组合对象
            Composite root = new Composite("服装");
            Composite c1 = new Composite("男装");
            Composite c2 = new Composite("女装");
    
            //定义所有的叶子对象
            Leaf leaf1 = new Leaf("衬衣");
            Leaf leaf2 = new Leaf("夹克");
            Leaf leaf3 = new Leaf("裙子");
            Leaf leaf4 = new Leaf("套装");
    
            //按照树的结构来组合组合对象和叶子对象
            root.addComposite(c1);
            root.addComposite(c2);
            c1.addLeaf(leaf1);
            c1.addLeaf(leaf2);
            c2.addLeaf(leaf3);
            c2.addLeaf(leaf4);
    
            //调用根对象的输出功能来输出整棵树
            root.printStruct("");
        }
    }
    

    上面的这种实现方案,虽然能实现了我们希望看到的功能,但是有一个很明显的问题:那就是必须区分组合对象和叶子对象,并进行有区别的对待。

    3. 组合模式

    3.1 定义

    组合模式(Composite Pattern)也叫合成模式,有时又叫做部分-整体模式(Part-Whole),主要是用来描述部分与整体的关系,其定义如下:

    Compose objects into tree structures to represent part-wholehierarchies.Composite lets clients treat individual objects and compositionsof objects uniformly.(将对象组合成树形结构以表示“部分-整体”的层次结构,使得用户对单个对象和组合对象的使用具有一致性。)

    3.2 通用类图

    • Component 抽象构件角色: 定义参加组合对象的共有方法和属性,可以定义一些默认的行为或属性。
    • Leaf 叶子构件: 叶子对象,其下再也没有其他的分支,也就是遍历的最小单位。
    • Composite 树枝构件: 树枝对象,它的作用是组合树枝节点和叶子节点形成一个树形结构。

    3.3 通用代码

    public abstract class Component {
        // 整体和个体都共享的逻辑
        void doSomething() {
            // 具体的业务逻辑
        }
    }
    
    public class Leaf extends Component {
        // 可以复写父类的方法
        @Override
        void doSomething() {
            super.doSomething();
        }
    }
    
    public class Composite extends Component {
        // 构建容器
        private ArrayList<Component> componentArrayList = new ArrayList<>();
        // 增加一个叶子构件或者树枝构件
        public void add(Component component) {
            this.componentArrayList.add(component);
        }
        // 删除一个叶子构件或者树枝构件
        public void remove(Component component) {
            this.componentArrayList.remove(component);
        }
        // 获得分支下所有叶子构件或者树枝构件
    
        public ArrayList<Component> getChildren() {
            return this.componentArrayList;
        }
    }
    

    3.4 优点

    1. 高层模块调用简单:

    一棵树形机构中的所有节点都是 Component ,局部和整体对调用者来说没有任何区别,也就是说,高层模块不必关心自己处理的是单个对象还是整个组合结构,简化了高层模块的代码。

    1. 节点自由增加:

    使用了组合模式后,我们可以看看,如果想增加一个树枝节点、树叶节点是不是都很容易,只要找到它的父节点就成,非常容易扩展,符合开闭原则,对以后的维护非常有利。

    4. 示例改进(安全模式)

    把最上面的示例修改成组合模式,首先需要定义一个抽象组件:

    public abstract class Component {
        // 输出组件名称
        abstract void printStruct(String preStr);
    }
    

    叶子节点:

    public class Leaf extends Component {
    
        private String name;
    
        public Leaf(String name) {
            this.name = name;
        }
    
        @Override
        void printStruct(String preStr) {
            System.out.println(preStr + " - " + name);
        }
    }
    

    树枝节点:

    public class Composite extends Component{
    
        // 组合对象集合
        private Collection<Component> childComponents;
    
        // 组合对象的名字
        private String name;
    
        public Composite(String name) {
            this.name = name;
        }
    
        public void addChild(Component child) {
            if (this.childComponents == null) {
                this.childComponents = new ArrayList<>();
            }
            this.childComponents.add(child);
        }
    
        void removeChild(Component child) {
            this.childComponents.remove(child);
        }
    
        Collection getChildren() {
            return this.childComponents;
        }
    
        @Override
        void printStruct(String preStr) {
            System.out.println(preStr + " + " + this.name);
            if (this.childComponents != null) {
                preStr+=" ";
                for(Component c : this.childComponents){
                    //递归输出每个子对象
                    c.printStruct(preStr);
                }
            }
        }
    }
    

    测试类:

    public class Test {
        public static void main(String[] args) {
            // 定义根节点
            Composite root = new Composite("服装");
            // 创建两个树枝节点
            Composite c1 = new Composite("男装");
            Composite c2 = new Composite("女装");
    
            // 定义所有的叶子对象
            Leaf leaf1 = new Leaf("衬衣");
            Leaf leaf2 = new Leaf("夹克");
            Leaf leaf3 = new Leaf("裙子");
            Leaf leaf4 = new Leaf("套装");
    
            // 按照树的结构来组合组合对象和叶子对象
            root.addChild(c1);
            root.addChild(c2);
            c1.addChild(leaf1);
            c1.addChild(leaf2);
            c2.addChild(leaf3);
            c2.addChild(leaf4);
            // 调用根对象的输出功能来输出整棵树
            root.printStruct("");
        }
    }
    

    5. 透明模式

    组合模式有两种不同的实现,安全模式和透明模式,上面的示例是安全模式,下面是透明模式的通用类图:

    和上面的那个安全模式的通用类图对比一下区别,就非常明显了,只是单纯的把几个方法 addChild()removeChild()getChildren() 几个方法放到了抽象类中。

    在透明模式中不管叶子对象还是树枝对象都有相同的结构,通过判断是否存在子节点来判断叶子节点还是树枝节点,如果处理不当,这个会在运行期出现问题。

    而在安全模式中,它是把树枝节点和树叶节点彻底分开,树枝节点单独拥有用来组合的方法,这种方案比较安全。

    抽象角色:

    public abstract class Component {
        // 输出组件名称
        abstract void printStruct(String preStr);
    
        // 向组合对象中加入组件对象
        abstract void addChild(Component child);
    
        // 从组合对象中移出某个组件对象
        abstract void removeChild(Component child);
    
        // 返回组件对象
        abstract Collection getChildren();
    }
    

    叶子节点:

    public class Leaf extends Component {
    
        private String name;
    
        public Leaf(String name) {
            this.name = name;
        }
    
        // 向组合对象中加入组件对象
        @Deprecated
        public void addChild(Component child) {
            // 缺省实现,如果子类未实现此功能,由父类抛出异常
            throw new UnsupportedOperationException("对象不支持这个功能");
        }
    
        // 从组合对象中移出某个组件对象
        @Deprecated
        public void removeChild(Component child){
            // 缺省实现,如果子类未实现此功能,由父类抛出异常
            throw new UnsupportedOperationException("对象不支持这个功能");
        }
    
        @Deprecated
        Collection getChildren() {
            throw new UnsupportedOperationException("对象不支持这个功能");
        }
    
        @Override
        void printStruct(String preStr) {
            System.out.println(preStr + " - " + name);
        }
    }
    

    这里使用 Deprecated 注解是为了在编译期告诉调用者,这方法我有,可以调用,但是已经失效了,如果一定要调用,那么在运行期会抛出 UnsupportedOperationException 的错误。

    树枝节点:

    public class Composite extends Component {
    
        // 组合对象集合
        private Collection<Component> childComponents;
    
        // 组合对象的名字
        private String name;
    
        public Composite(String name) {
            this.name = name;
        }
    
        public void addChild(Component child) {
            if (this.childComponents == null) {
                this.childComponents = new ArrayList<>();
            }
            this.childComponents.add(child);
        }
    
        @Override
        void removeChild(Component child) {
            this.childComponents.remove(child);
        }
    
        @Override
        Collection getChildren() {
            return this.childComponents;
        }
    
        @Override
        void printStruct(String preStr) {
            System.out.println(preStr + " + " + this.name);
            if (this.childComponents != null) {
                preStr+=" ";
                for(Component c : this.childComponents){
                    //递归输出每个子对象
                    c.printStruct(preStr);
                }
            }
        }
    }
    

    测试类:

    public class Test {
        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("");
        }
    }
    

    6. 参考

    https://www.jianshu.com/p/dead42334033

  • 相关阅读:
    二、JVM内存模型
    一、计算机内存与JVM内存简析
    Centos7编译openjdk7
    linux查看CPU内核信息
    HashMap源码解析
    windows下安装MongoDB要注意的问题
    Javascript理解this对象
    关于javascript闭包中的this对象
    mac系统 IDEA+JFinal+Tomcat+Maven搭建
    Mac下python3配置Sklearn
  • 原文地址:https://www.cnblogs.com/babycomeon/p/14022792.html
Copyright © 2011-2022 走看看