• 设计模式之组合模式


    组合模式

    定义:将对象组合成树形结构以表示“部分-整体”的层次结构,使客户端对单个对象和组合对象保持一致的方式处理

    类型:结构型

    适用场景

    • 希望客户端可以忽略组合对象与单个对象的差异时
    • 处理一个树形结构时

    优点

    • 清楚地定义分层次的负责对象,表示对象的全部或部分层次
    • 让客户端忽略了层次的差异,方便对整个层次结构进行控制
    • 简化客户端的代码
    • 符合开闭原则

    缺点

    • 限制类型时会比较复杂
    • 使设计变得更加抽象

    组合模式扩展说明

    合成模式的实现根据所实现接口的区别分为两种形式,分别称为安全式透明式

    示例

    • 文件系统就是典型的组合模式,如下图是window系统D盘符下的部分目录组织结构,红橙色的是目录,浅绿色的是文件,这里目录和文件是可以看成是同一种对象对待

    合成模式可以不提供父对象的管理方法,但是合成模式必须在合适的地方提供子对象的管理方法,诸如:add()、remove()、以及getChild()等

    安全式合成模式

     安全模式的合成模式要求管理聚集的方法只出现在树枝构件类中,而不出现在树叶构件类中

    抽象构件(Component)角色:这是一个抽象角色,它给参加组合的对象定义出公共的接口及其默认行为,可以用来管理所有的子对象。合成对象通常把它所包含的子对象当做类型为Component的对象。在安全式的合成模式里,构件角色并不定义出管理子对象的方法,这一定义由树枝构件对象给出

    树叶构件(Leaf)角色:树叶对象是没有下级子对象的对象,定义出参加组合的原始对象的行为。

    树枝构件(Composite)角色:代表参加组合的有下级子对象的对象。树枝构件类给出所有的管理子对象的方法,如add()、remove()以及getChild()。

    • 安全式合成模式代码:
    /**
     * 抽象构件角色类
     */
    public interface Component {
    
        /**
         * 输出组件的名称
         * @param preStr
         */
        void printStruct(String preStr);
    
    }
    
    /**
     * 树枝构件角色类(目录)
     */
    public class Composite implements Component {
    
        /**
         * 存储组合对象中包含的子组件的对象(可能是目录也可能是文件)
         */
        private List<Component> childComponents = new ArrayList<>();
    
        /**
         * 组合对象的名称
         */
        private String name;
    
        /**
         * 构件名称
         * @param name
         */
        public Composite(String name) {
            this.name = name;
        }
    
    
        /**
         * 输出对象的自身结构
         * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            // 先把自己输出
            System.out.println(preStr + "+" + this.name);
            //如果还包含有子组件,那么就输出这些子组件对象
            if(this.childComponents != null){
                //添加两个空格,表示向后缩进两个空格
                preStr += "  ";
                //输出当前对象的子对象
                for(Component c : childComponents){
                    //递归输出每个子对象
                    c.printStruct(preStr);
                }
            }
        }
    
        /**
         * 聚集管理方法,增加一个子构件对象
         * @param child 子构件对象
         */
        public void addChild(Component child){
            childComponents.add(child);
        }
    
        /**
         * 聚集管理方法,删除一个子构件对象
         * @param index 子构件对象的下标
         */
        public void removeChild(int index){
            childComponents.remove(index);
        }
    
        /**
         * 聚集管理方法,返回所有子构件对象
         */
        public List<Component> getChild(){
            return childComponents;
        }
    }
    
    /**
     * 叶子构件
     */
    public class Leaf implements Component {
    
        /**
         * 叶子对象的名字
         */
        private String name;
        /**
         * 构造方法,传入叶子对象的名称
         * @param name 叶子对象的名字
         */
        public Leaf(String name){
            this.name = name;
        }
        /**
         * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
         * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            System.out.println(preStr + "-" + name);
        }
    }
    
    /**
     * 安全模式(客户端)
     */
    public class Client {
    
        public static void main(String[] args) {
            Composite root = new Composite("D盘");
            Composite favoriteDir = new Composite("收藏夹");
            Composite picDir = new Composite("图片");
            picDir.addChild(new Leaf("美女1.png"));
            picDir.addChild(new Leaf("美女2.png"));		
    
            Composite workDir = new Composite("工作");
            workDir.addChild(new Leaf("需求文档.doc"));
            Composite learningDir = new Composite("学习");
            learningDir.addChild(new Leaf("Java笔记.md"));
            learningDir.addChild(new Leaf("数据库笔记.doc"));
    
            root.addChild(favoriteDir);
            root.addChild(workDir);
            root.addChild(learningDir);
            root.addChild(picDir);
            root.addChild(new Leaf("log.txt"));
            
            //错误演示
            //Leaf leaf = new Leaf("test.txt");
            //leaf.addChild(new Leaf("log.txt"))//报错,编译不通过,安全模式下不会出现错误调用
            
    		//输出整个目录结构
            root.printStruct("");
    
        }
    
    }
    

    +D盘
    +收藏夹
    +工作
    -需求文档.doc
    +学习
    -Java笔记.md
    -数据库笔记.doc
    +图片
    -美女1.png
    -美女2.png
    -log.txt

    可以看出,树枝构件类(Composite)给出了addChild()、removeChild()以及getChild()等方法的声明和实现,而树叶构件类则没有给出这些方法的声明或实现。这样的做法是安全的,由于这个特点,客户端应用程序不可能错误地调用树叶构件的聚集方法,因为树叶构件没有这些方法,调用会导致编译错误。但是,这种安全模式却有违背组合模式原本的定义,使客户端对单个对象和组合对象保持一致的方式处理

    透明式合成模式

    • 与安全式的合成模式不同的是,透明式的合成模式要求所有的具体构件类,不论树枝构件还是树叶构件,均符合一个固定接口

    • 透明合成模式代码
    /**
     * 抽象构件角色类
     */
    public abstract class Component {
    
        /**
         * 输出组件自身的名称
         */
        public abstract void printStruct(String preStr);
    
        /**
         * 聚集管理方法,增加一个子构件对象
         * @param child 子构件对象
         */
        public void addChild(Component child){
            /**
             * 缺省实现,抛出异常,因为叶子对象没有此功能
             * 或者子组件没有实现这个功能
             */
            throw new UnsupportedOperationException("对象不支持此功能");
        }
    
        /**
         * 聚集管理方法,删除一个子构件对象
         * @param index 子构件对象的下标
         */
        public void removeChild(int index){
            /**
             * 缺省实现,抛出异常,因为叶子对象没有此功能
             * 或者子组件没有实现这个功能
             */
            throw new UnsupportedOperationException("对象不支持此功能");
        }
    
        /**
         * 聚集管理方法,返回所有子构件对象
         */
        public List<Component> getChild(){
            /**
             * 缺省实现,抛出异常,因为叶子对象没有此功能
             * 或者子组件没有实现这个功能
             */
            throw new UnsupportedOperationException("对象不支持此功能");
        }
    }
    
    /**
     * 树枝构件角色类,此类将implements Conponent改为extends Conponent,其他地方无变化
     */
    public class Composite extends Component {
    
        /**
         * 用来存储组合对象中包含的子组件对象
         */
        private List<Component> childComponents = new ArrayList<Component>();
    
        /**
         * 组合对象的名字
         */
        private String name;
    
        /**
         * 构造方法,传入组合对象的名字
         * @param name    组合对象的名字
         */
        public Composite(String name){
            this.name = name;
        }
    
        /**
         * 聚集管理方法,增加一个子构件对象
         * @param child 子构件对象
         */
        public void addChild(Component child){
            childComponents.add(child);
        }
    
        /**
         * 聚集管理方法,删除一个子构件对象
         * @param index 子构件对象的下标
         */
        public void removeChild(int index){
            childComponents.remove(index);
        }
    
        /**
         * 聚集管理方法,返回所有子构件对象
         */
        public List<Component> getChild(){
            return childComponents;
        }
    
        /**
         * 输出对象的自身结构
         * @param preStr 前缀,主要是按照层级拼接空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            // 先把自己输出
            System.out.println(preStr + "+" + this.name);
            //如果还包含有子组件,那么就输出这些子组件对象
            if(this.childComponents != null){
                //添加两个空格,表示向后缩进两个空格
                preStr += "  ";
                //输出当前对象的子对象
                for(Component c : childComponents){
                    //递归输出每个子对象
                    c.printStruct(preStr);
                }
            }
    
        }
    
    }
    
    /**
     * 树叶构件角色类,此类将implements Conponent改为extends Conponent,其他地方无变化
     */
    public class Leaf extends Component {
        /**
         * 叶子对象的名字
         */
        private String name;
        /**
         * 构造方法,传入叶子对象的名称
         * @param name 叶子对象的名字
         */
        public Leaf(String name){
            this.name = name;
        }
        /**
         * 输出叶子对象的结构,叶子对象没有子对象,也就是输出叶子对象的名字
         * @param preStr 前缀,主要是按照层级拼接的空格,实现向后缩进
         */
        @Override
        public void printStruct(String preStr) {
            System.out.println(preStr + "-" + name);
        }
    
    }
    
    /**
     * 透明模式(客户端)
     */
    public class Client {
    
        public static void main(String[] args) {
            Component root = new Composite("D盘");
            Component favoriteDir = new Composite("收藏夹");
            Component picDir = new Composite("图片");
            picDir.addChild(new Leaf("美女1.png"));
            picDir.addChild(new Leaf("美女2.png"));
    
    
            Component workDir = new Composite("工作");
            workDir.addChild(new Leaf("需求文档.doc"));
            Component learningDir = new Composite("学习");
            learningDir.addChild(new Leaf("Java笔记.md"));
            learningDir.addChild(new Leaf("数据库笔记.doc"));
    
            root.addChild(favoriteDir);
            root.addChild(workDir);
            root.addChild(learningDir);
            root.addChild(picDir);
            root.addChild(new Leaf("log.txt"));
    
            //Component leaf = new Leaf("test");
            //leaf.addChild(new Leaf("log.txt"));//编译通过,运行报错
    
            root.printStruct("");
    
        }
    
    }
    

    可以看出,客户端无需再区分操作的是树枝对象(Composite)还是树叶对象(Leaf)了;对于客户端而言,操作的都是Component对象。

    总结

    这里所说的安全性组合模式是指:从客户端使用合成模式上看是否更安全,如果是安全的,那么就不会有发生误操作的可能,能访问的方法都是被支持的。

    这里所说的透明性组合模式是指:从客户端使用合成模式上,是否需要区分到底是“树枝对象”还是“树叶对象”。如果是透明的,那就不用区分,对于客户而言,都是Compoent对象,具体的类型对于客户端而言是透明的,是无须关心的。

    对于合成模式而言,在安全性和透明性上,会更看重透明性,毕竟合成模式的目的是:让客户端不再区分操作的是树枝对象还是树叶对象,而是以一个统一的方式来操作。

    而且对于安全性的实现,需要区分是树枝对象还是树叶对象。有时候,需要将对象进行类型转换,却发现类型信息丢失了,只好强行转换,这种类型转换必然是不够安全的。因此在使用合成模式的时候,建议多采用透明性的实现方式

    相关的设计模式

    • 组合模式和访问者模式

    可以使用访问者模式来访问组合模式中的递归结构

    使用典范

    • java.awt.Container
    • java.util.HashMap#putAll
    • Java.util.ArrayList#addAll
    • org.apache.ibatis.scripting.xmltags.SqlNode/org.apache.ibatis.scripting.xmltags.MixedSqlNode

    参考

  • 相关阅读:
    iOS实时查看App运行日志
    Jmeter-使用Ultimate Thread Group插件来设置负载场景
    Flask使用Flask-SQLAlchemy操作MySQL数据库
    使用requests库提交multipart/form-data 格式的请求
    spark 性能调优(一) 性能调优的本质、spark资源使用原理、调优要点分析
    一、spark错误
    sqoop 补充
    Hbase—— rowkey 过滤器(rowfilter)
    spark 调优——基础篇
    scala 的安装 与 IDEA安装使用
  • 原文地址:https://www.cnblogs.com/DiDi516/p/12733106.html
Copyright © 2020-2023  润新知