二十三种设计模式(九)--组合模式

组合模式Composite

组合模式用于解决目标对象群为树形结构的问题, 能够用统一的方法对任意对象进行批量管理. 比如, 学校-院系-班级, 公司-管理层-工作部门.

组合模式的最小结构由3部分组成,

  1. 统一接口 LeafInterface
  2. 树枝(树枝上连接若干叶子节点) class Branch
  3. 叶子节点 class Leaf

最简组合模式定义示例:

java 复制代码
// 叶子与树枝统一的接口
interface LeafInterface {
    void swing();
}

// 叶子节点, 最终执行任务的节点.
class Leaf implements LeafInterface {
    String name;
    Leaf(String name) {
        this.name = name;
    }

    public void swing() {
        System.out.println("leaf " + name + " swings.");
    }
}

// 树枝节点, 存储很多叶子, 也可以存储很多树枝
class Branch implements LeafInterface {
    // 这里是组合模式的的关键, 树枝列表的数据类型要和叶子节点保持一致, 都是LeafInterface
    List<LeafInterface> leafs = new ArrayList<>();

    @Override
    public void swing() {
        for (int i = 0; i < leafs.size(); i++) {
            leafs.get(i).swing();
        }
    }

    public int add(LeafInterface w) {
        leafs.add(w);
        return leafs.size();
    }
}

调用示例:

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class CompositePattern {
    public static void main(String[] args) {
        // 创建叶子节点
        Leaf l1 = new Leaf("1-l1");
        Leaf l2 = new Leaf("1-l2");
        Leaf l3 = new Leaf("1-l3");

        // 创建树枝
        Branch b1 = new Branch();

        // 树枝上添加叶子
        b1.add(l1);
        b1.add(l2);
        b1.add(l3);

        Branch b2 = new Branch();
        b2.add(new Leaf("2-l1"));
        b2.add(new Leaf("2-l2"));


        Branch b3 = new Branch();
        b3.add(new Leaf("3-l1"));
        b3.add(new Leaf("3-l2"));
        // 树枝上也可以添加其他树枝
        b2.add(b3);
        b1.add(b2);


        // 执行时, 既可以执行单个叶片的swing方法
        // 也可以执行树枝的swing方法
        System.out.println("========= 单片树叶摇摆");
        l1.swing();
        l3.swing();
        System.out.println("========= 整个树枝上所有的叶片摇摆");
        b1.swing();
    }
}

运行结果:

复制代码
========= 单片树叶摇摆
leaf 1-l1 swings.
leaf 1-l3 swings.
========= 整个树枝上所有的叶片摇摆
leaf 1-l1 swings.
leaf 1-l2 swings.
leaf 1-l3 swings.
leaf 2-l1 swings.
leaf 2-l2 swings.
leaf 3-l1 swings.
leaf 3-l2 swings.

在具体业务中的使用示例如下:

java 复制代码
// 统一接口
interface MenuComponent {

    String getName();

    void print(String indent); // 实现统一操作, 这里只打印
}

// 叶子节点 Leaf
class MenuItem implements MenuComponent {

    private final String name;
    private final String path;

    public MenuItem(String name, String path) {
        this.name = name;
        this.path = path;
    }

    @Override
    public String getName() {
        return name;
    }

    public String getPath() {
        return path;
    }

    @Override
    public void print(String indent) {
        System.out.println(indent + "- " + name + " (" + path + ")");
    }
}

// 树枝节点 Branch
class Menu implements MenuComponent {

    private final String name;
    private final List<MenuComponent> children = new ArrayList<>();

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

    public void add(MenuComponent component) {
        children.add(component);
    }

    public void remove(MenuComponent component) {
        children.remove(component);
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public void print(String indent) {
        System.out.println(indent + "[Menu] " + name);
        for (MenuComponent child : children) {
            child.print(indent + "  ");
        }
    }
}

调用示例:

java 复制代码
public class CompositeTest {
    public static void main(String[] args) {
        // 一级菜单
        Menu systemMenu = new Menu("系统管理");

        // 二级菜单
        Menu userMenu = new Menu("用户管理");
        Menu roleMenu = new Menu("角色管理");

        // 二级子菜单项
        userMenu.add(new MenuItem("用户列表", "/system/user/list"));
        userMenu.add(new MenuItem("新增用户", "/system/user/add"));

        roleMenu.add(new MenuItem("角色列表", "/system/role/list"));
        roleMenu.add(new MenuItem("分配权限", "/system/role/assign"));

        // 总菜单添加子菜单
        systemMenu.add(userMenu);
        systemMenu.add(roleMenu);

        // 其他一级菜单
        Menu reportMenu = new Menu("报表中心");
        reportMenu.add(new MenuItem("销售报表", "/report/sales"));
        reportMenu.add(new MenuItem("库存报表", "/report/inventory"));

        // 根菜单(系统所有菜单)
        Menu root = new Menu("系统根目录");
        root.add(systemMenu);
        root.add(reportMenu);

        // 打印菜单树
        root.print("");
    }
}

运行结果

复制代码
[Menu] 系统根目录
  [Menu] 系统管理
    [Menu] 用户管理
      - 用户列表 (/system/user/list)
      - 新增用户 (/system/user/add)
    [Menu] 角色管理
      - 角色列表 (/system/role/list)
      - 分配权限 (/system/role/assign)
  [Menu] 报表中心
    - 销售报表 (/report/sales)
    - 库存报表 (/report/inventory)

总结组合模式:

无论你是一个节点,还是一整棵子树,我都用一个接口对你进行操作。

复制代码
1. 统一处理复杂结构(不用写多个 if/else)无需区分"节点 与子树"。
  调用任意节点的通用方法都可以遍历执行该节点下所有节点的通用方法
2. 扩展性极强(新增节点类型不用改老代码)只扩展,不修改,天然符合 OCP。
3. 层级结构清晰,自带递归处理能力. 非常适合:菜单、权限、组织架构、文件系统。
4. 降低客户端复杂度, 客户端面对的永远是 Component 接口,这极大降低了调用者复杂度。
5. 组合节点本身也是组件 → 节点和树等价, 这种"树的每个节点都是树结构"的特性让架构更灵活。
6. 非常利于 JSON 输出、前端渲染、数据库查询, 树状结构非常适合直接转换为 JSON,在前后端接口设计上使用广泛。

所符合的设计原则:

复制代码
1. 单一职责原则(SRP)每个类职责清晰,不相互混杂。
	Component:定义行为
	Leaf:实现基本行为
	Composite:负责包含子对象、递归管理
	
2. 开闭原则(OCP)--- 最重要的原则
	组合模式对扩展开放,对修改关闭, 新增一种节点 → 不需要改已有代码
	树结构自动支持它, 客户端依旧只关注 Component 接口

3. 依赖倒置原则(DIP)
	客户端依赖抽象(Component),而不是具体实现(Leaf、Composite)。

4. 里氏替换原则(LSP)
	Leaf 和 Composite 都实现 Component,
	在客户端看来都是 Component,随时替换不会出错。

5. 迪米特法则(LoD) 类之间之和直接相关的朋友交互
	客户端只调用 Component,不需知道 Composite 内部怎么管理子节点。
相关推荐
哈哈哈笑什么2 小时前
3 次生产系统崩溃复盘:Java 后端从踩坑到封神的排查优化之路
java·后端·性能优化
用户3721574261352 小时前
如何在 Java 中将 RTF 转换为 PDF (含批量转换)
java
谷哥的小弟3 小时前
Spring Framework源码解析——ApplicationContextException
java·spring·源码
学到头秃的suhian3 小时前
Springboot进阶知识
java·spring boot·spring
你想知道什么?3 小时前
JNI简单学习(java调用C/C++)
java·c语言·学习
期待のcode3 小时前
Thymeleaf模板引擎
java·html·springboot
白宇横流学长3 小时前
基于SpringBoot实现的电子发票管理系统
java·spring boot·后端
白宇横流学长3 小时前
基于SpringBoot实现的智慧就业管理系统
java·spring boot·后端
weixin_462446233 小时前
EasyExcel 动态修改模板 Sheet 名称:自定义 SheetWriteHandler 拦截器
java·开发语言·easyexcel
赵庆明老师3 小时前
NET 使用SmtpClient 发送邮件
java·服务器·前端