自学教程 学编程从这里开始

组合模式

《组合模式》|设计模式系列第09篇

系列:结构型模式 · 组合模式(Composite Pattern)
关键词:树形结构、部分-整体、叶子节点、容器节点、统一递归访问

一、什么是组合模式

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

属于结构型设计模式。

简单理解:
把叶子节点(单个对象)和容器节点(包含子节点的对象)实现同一套接口。客户端不需要区分当前操作的是单个对象,还是包含一堆子对象的容器,调用方式完全一致。典型场景:文件目录树,文件夹里面可以放文件,也可以放子文件夹;文件和文件夹对外都支持展示名称、删除等操作。

核心意图
统一叶子对象与组合对象的访问方式,方便操作树形结构,客户端不用区分节点类型。

适用场景

  1. 需要表示对象的部分-整体树形层次结构
  2. 希望客户端可以统一对待单个对象和组合对象
  3. 遍历树形结构,递归处理节点
  4. 菜单、组织架构、文件目录、权限树等场景

⚠️ 缺点预警:设计较抽象;如果节点类型差异很大,很难定义统一接口;限制叶子节点和容器节点的行为。

二、UML结构图

角色划分:

  1. Component(抽象构件):叶子和容器统一的抽象接口,声明公共操作
  2. Leaf(叶子构件):叶子节点,没有子节点,实现Component接口
  3. Composite(容器构件):容器节点,包含子节点集合,实现Component接口,管理子节点
  4. Client(客户端):面向Component抽象编程,无需区分叶子/容器

三、Java 完整示例

场景:文件系统。文件(叶子)、文件夹(容器),统一支持展示名称,文件夹可以增加子节点。

1. 抽象构件 Component

public abstract class FileSystemNode {
    protected String name;

    public FileSystemNode(String name) {
        this.name = name;
    }

    public abstract void show(int depth);

    // 容器节点才需要的方法,叶子节点空实现
    public boolean add(FileSystemNode node) {
        throw new UnsupportedOperationException();
    }

    public boolean remove(FileSystemNode node) {
        throw new UnsupportedOperationException();
    }
}

2. 叶子构件 Leaf(文件)

public class File extends FileSystemNode {
    public File(String name) {
        super(name);
    }

    @Override
    public void show(int depth) {
        // 打印缩进,模拟层级
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
    }
}

3. 容器构件 Composite(文件夹)

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

public class Folder extends FileSystemNode {
    private List<FileSystemNode> childList = new ArrayList<>();

    public Folder(String name) {
        super(name);
    }

    @Override
    public boolean add(FileSystemNode node) {
        childList.add(node);
        return true;
    }

    @Override
    public boolean remove(FileSystemNode node) {
        childList.remove(node);
        return true;
    }

    @Override
    public void show(int depth) {
        for (int i = 0; i < depth; i++) {
            System.out.print("-");
        }
        System.out.println(name);
        // 递归展示子节点,层级+1
        for (FileSystemNode child : childList) {
            child.show(depth + 1);
        }
    }
}

4. 客户端调用

public class Client {
    public static void main(String[] args) {
        FileSystemNode root = new Folder("根目录");
        FileSystemNode docFolder = new Folder("文档");
        FileSystemNode picFolder = new Folder("图片");

        FileSystemNode readme = new File("readme.txt");
        FileSystemNode photo = new File("风景.jpg");

        docFolder.add(readme);
        picFolder.add(photo);
        root.add(docFolder);
        root.add(picFolder);

        // 统一调用show,不用区分是文件还是文件夹
        root.show(0);
    }
}

客户端只依赖 FileSystemNode,调用 show(),不管是文件还是文件夹,调用方式完全一样。

四、两种实现方式

  1. 透明模式(示例代码这种):add/remove 放在抽象构件。叶子节点继承后抛出异常;优点:客户端完全不用区分节点;缺点:叶子有无效方法。
  2. 安全模式:add/remove 只放在 Composite 容器。优点:叶子不会有无效方法;缺点:客户端需要判断类型才能增删子节点。

开发常用透明模式,牺牲一点接口纯净度换取客户端使用一致性。

五、优缺点总结

✅ 优点

  1. 统一操作叶子与容器对象,简化客户端代码
  2. 树形结构扩展方便,新增叶子/容器都不需要修改原有代码,符合开闭原则
  3. 方便递归遍历树形数据

❌ 缺点

  1. 抽象设计难度高,很难为所有构件定义统一行为接口
  2. 透明模式下叶子节点会拥有不需要的 add/remove 方法,违背接口隔离原则

六、框架源码应用

  • JDK java.awt.Container / Component
  • MyBatis SqlNode,动态SQL节点树,大量使用组合模式
  • Spring CompositeFileListFilter

七、小结

组合模式核心:统一叶子和容器的接口,用树形结构表达部分与整体,客户端无差别调用。
适合树形层级结构,比如菜单、组织树、文件目录。核心判断点:是否需要对单个对象和一组对象做相同操作。

标签:

0 条笔记