秒懂设计模式--学习笔记(8)【结构型-组合模式】

发布于:2024-07-11 ⋅ 阅读:(21) ⋅ 点赞:(0)

7、组合模式

7.1 组合模式(Composite)
  • 是针对由多个节点对象(部分)组成的树形结构的对象(整体)而发展出的一种结构型设计模式
  • 它能够使客户端在操作整体对象或者其下的每个节点对象时做出统一的响应,保证树形结构对象使用方法的一致性
  • 整体和部分,有类似结构
  • 测试类结构
    组合模式测试类
7.2 叉树结构
  • 某些具有从属关系的事物之间存在着一定的相似性
  • 不管从哪个层级,我们都会得到一个固定的结构
  • 这种结构类似于经典的“叉树”结构:无论数据元素是“根”“枝”,还是“叶”,甚至是整体的树,都具有类似的结构
  • 我们可以用组合模式来表达“部分/整体”的层次结构提取并抽象其相同的部分特殊化其不同的部分,以提高系统的可复用性与可扩展性
7.3 文件系统
  • 以类似于树结构的文件系统的目录结构为例
  • 抽象节点类Node
    • 定义一个抽象的“节点”类来模糊“文件夹”与“文件”
    • 构造方法中接收并初始化已定义的节点名,否则不允许节点被创建,这也是可以固化下来的逻辑
    • 声明抽象方法,模糊行为并留给子类去实现
package composite;

/**
 * 抽象节点类
 **/
public abstract class Node {
    // 节点命名
    protected  String name;

    /**
     * 构造方法,需传入节点名
     *  构造方法中接收并初始化已定义的节点名,否则不允许节点被创建,这也是可以【固化下来的逻辑】
     */
    public Node(String name) {
        this.name = name;
    }

    /**
     * 添加下级节点方法: 声明其为抽象方法,模糊此行为并留给子类去实现
     * @param child
     */
    protected abstract void add(Node child) throws Exception;
}
  • 节点实现类1:文件夹类Folder
    • 文件夹类继承了抽象节点类Node
    • 文件夹下级可以包含任意多个文件夹或者文件:次级节点列表List
package composite;

import java.util.ArrayList;
import java.util.List;

/**
 * 文件夹类:继承了抽象节点类Node
 **/
public class Folder extends Node {
    /**
     * 文件夹可以包含子节点(子文件夹或文件)
     *  此处的泛型Node既可以是文件夹又可以是文件
     */
    List<Node> children = new ArrayList<>();

    /**
     * 调用父类构造方法:初始化其文件夹名
     * @param name
     */
    public Folder(String name) {
        super(name);
    }

    /**
     * 重写添加方法:可以添加子节点
     * @param child
     */
    @Override
    protected void add(Node child) {
        children.add(child);
    }
}
  • 节点实现类2:文件类File
    • 其作为末端节点,不应该具备添加子节点的功能
package composite;

/**
 * 文件类:继承了抽象节点类Node
 **/
public class File extends Node {
    /**
     * 调用父类构造方法:初始化其文件夹名
     * @param name
     */
    public File(String name) {
        super(name);
    }

    /**
     * 文件不包含子节点: 告知用户“不能添加子节点”
     * 其实更好的方式是以抛出异常的形式来确保此处逻辑的正确性,外部如果捕获到该异常则可以做出相应的处理
     * @param child
     */
    @Override
    protected void add(Node child) {
        System.out.println("文件类型不能添加子节点");
    }
}
  • 客户端测试类:创建文件夹及文件
package composite;

/**
 * 客户端测试类
 **/
public class Client {
    public static void main(String[] args) throws Exception {
        // 为根节点构建了目录树
        Node driveD = new Folder("测试盘");

        // “文档”和“音乐”两个文件夹
        Node doc = new Folder("文档");
        doc.add(new File("简历.doc"));
        doc.add(new File("项目介绍.ppt"));
        driveD.add(doc);

        // “文档”和“音乐”两个文件夹
        Node music = new Folder("音乐");
        // 一级文件夹来区分歌手
        Node jay = new Folder("周杰伦");
        jay.add(new File("双截棍.mp3"));
        jay.add(new File("告白气球.mp3"));
        jay.add(new File("听妈妈的话.mp3"));
        // 一级文件夹来区分歌手
        Node jack = new Folder("张学友");
        jack.add(new File("吻别.mp3"));
        jack.add(new File("一千个伤心的理由.mp3"));

        music.add(jay);
        music.add(jack);
        driveD.add(music);
        
    }
}
7.4 目录树展示
  • 要体现出组合模式的优势还在于如何运用这个树结构
  • 分级展示整棵目录树
    • 输出节点名称(文件夹名/文件名)之前加上数个空格以表示不同层级
    • 修改抽象节点类Node并加入展示方法tree()
        /**
         * 此处是抽象节点类的实体方法,所以要保持其通用性。
         *      我们抽离出所有节点“相同”的部分作为“公有”的代码块,
         *      而“不同”的行为部分则留给子类去实现
         * @param space 空格数(层级缩进展示)
         */
        protected void  tree(int space) {
            //先循环输出space个空格
            for (int i = 0; i < space; i++) {
                System.out.print("    ");
            }
    
            //接着再输出自己的名字
            System.out.println(name);
        }
    
        /**
         * 无参重载方法,默认从第0列开始展示(无层级缩进)
         */
        protected void tree() {
            this.tree(0);
        }
    
    • 节点实现类File和Folder重写展示方法,Folder还要展示其子级
        /**
         * Folder
         * 文件夹类就比较特殊了,它不仅要先输出自己的名字,还要换行再逐个输出子节点的名字,并且要保证空格逐级递增
         */
        @Override
        public void tree(int space) {
            // 调用父类通用的tree方法列出自己的名字
            super.tree(space);
    
            // 在循环的子节点前,空格数要加1
            space++;
            // 下一级的子节点我们需要依次输出
            for (Node child : children) {
                // 调用子节点的tree方法
                child.tree(space);
            }
        }
    
    /**
     * File
     * 文件类可以不做任何修改,而是直接继承父类的展示方法,
     * 此处是为了更清晰直观地看到这种继承关系,同时方便后续做出其他修改
     * @param space
     */
    @Override
    public void tree(int space){
        super.tree(space);
    }
    
    • 客户端在任何一级节点上只要调用其展示方法并传入当前目录所需的空格偏移量,就可出现树形列表了
    	// Client类的main方法最后添加调用目录树展示方法
        driveD.tree();
    
    • 空格偏移量这个必传参数,可以为抽象节点类添加一个无参的展示方法,默认为0
7.5 自相似性的涌现
  • 组合模式将树形结构的特点发挥得淋漓尽致
    • 作为最高层级抽象的抽象节点类(接口)泛化了所有节点类使任何“整体”或“部分”达成统一
    • 枝(根)节点与叶节点的多态化实现以及组合关系进一步勾勒出的树形结构
7.6 组合模式的各角色定义
  • Component(组件接口):
    • 所有复合节点与叶节点的高层抽象,定义出需要对组件操作的接口标准
    • 如抽象节点类Node,具体使用接口还是抽象类需根据具体场景而定。
  • Composite(复合组件):
    • 包含多个子组件对象(可以是复合组件或叶端组件)的复合型组件,并实现组件接口中定义的操作方法。
    • 如:“根节点/枝节点”的文件夹类Folder
  • Leaf(叶端组件):
    • 不包含子组件的终端组件,同样实现组件接口中定义的操作方法。
    • 如: “叶节点”的文件类File
  • Client(客户端):
    • 按所需的层级关系部署相关对象并操作组件接口所定义的接口,即可遍历树结构上的所有组件
7.7 组合
  • 类似的结构总是在重复、迭代地显现出某种自似性
  • 其部分与整体一致的呈现与“组合模式”如出一辙