Java 组合模式从入门到实战(后端必看,附案例+面试考点)

前言:组合模式(Composite Pattern)是Java设计模式中最实用的“树形结构管理神器”之一,属于结构型模式,核心是“将对象组合成树形结构,以表示‘部分-整体’的层次关系,让客户端对单个对象和组合对象的访问具有一致性”。

很多Java后端开发者在面对树形结构场景(如部门组织架构、菜单导航、文件目录)时,容易写出冗长、繁琐的嵌套代码,难以维护和扩展;面试时被问到“组合模式的核心是什么”“JDK中哪里用到了组合模式”“组合模式和装饰器模式的区别”,常常无从下手。

本文从入门到实战,用极简语言拆解组合模式核心,结合可直接复制运行的代码案例、真实业务场景(部门架构、菜单管理),以及高频面试考点,带你吃透组合模式,新手也能快速上手,看完就能落地到项目中。

一、为什么Java后端必须掌握组合模式?(痛点直击)

先看3个Java后端开发中最常见的树形结构场景,你一定遇到过,这也是组合模式的核心应用场景:

  • 场景1:企业部门管理系统中,公司有总部、分公司、部门、小组,形成层级树形结构(总部→分公司→部门→小组),需要统一查询某个节点下的所有子节点、统计部门人数,若逐个处理单个节点和组合节点,代码会极其繁琐;

  • 场景2:后台管理系统的菜单导航,有一级菜单、二级菜单、三级菜单,需要统一渲染所有菜单、判断菜单是否有子菜单、点击菜单触发统一逻辑,若单独处理不同层级的菜单,扩展性极差;

  • 场景3:面试时,面试官追问“Spring MVC的HandlerMapping底层如何管理请求路径”“JDK的集合框架中哪些类用到了组合模式”,其实这些都是组合模式的典型应用,答不上来就会错失加分机会。

而组合模式的核心价值,就是通过树形结构统一管理“单个对象”和“组合对象”,让客户端无需区分两者,采用一致的方式访问,从而简化代码、提升扩展性

简单说,就是“单个和组合,待遇都一样”——就像公司的组织架构,无论是单个员工(叶子节点),还是整个部门(组合节点),都可以统一统计人数、统一管理,无需单独处理。

核心结论:组合模式不是“花里胡哨”的设计,而是后端开发的“树形结构刚需”——初级开发者用它简化树形结构的操作逻辑,中级开发者用它设计可扩展的层级架构,高级开发者用它理解框架底层的树形管理逻辑(如Spring MVC、MyBatis),面试时更是高频考点(尤其是中高级岗位)。

二、组合模式核心概念(极简入门,无需死记硬背)

组合模式的本质很简单:将“单个对象”(叶子节点)和“组合对象”(容器节点)统一继承自同一个抽象类/接口,组合对象内部维护一个子节点集合,从而形成树形结构,客户端通过抽象接口访问所有节点,无需区分类型

就像部门架构:部门(组合节点)可以包含子部门(组合节点)和员工(叶子节点),无论是部门还是员工,都有“名称”“编号”,都能执行“统计人数”的操作,客户端无需区分是部门还是员工,直接调用统一方法即可。

2.1 核心角色(3个核心,必记,区分叶子和容器是关键)

  1. 抽象构件角色(Component):定义所有节点(叶子节点和组合节点)的统一接口,声明公共方法(如获取名称、统计数量、添加/删除子节点),是组合模式的核心,确保客户端访问的一致性;

  2. 叶子构件角色(Leaf):表示树形结构中的“单个对象”,没有子节点,实现抽象构件的公共方法,是树形结构的最底层节点(如员工、单个菜单、单个文件);

  3. 组合构件角色(Composite):表示树形结构中的“组合对象”,可以包含子节点(叶子节点或其他组合节点),实现抽象构件的公共方法,同时维护一个子节点集合,提供添加、删除、遍历子节点的方法(如部门、菜单组、文件夹)。

核心原则:抽象构件统一接口,叶子节点无子类,组合节点管理子类,客户端通过抽象接口访问所有节点,无需区分叶子和组合。这是组合模式的灵魂,也是组合模式与其他结构型模式的关键区别。

核心注意点:组合模式的核心是“统一访问”和“树形结构管理”,解决的是“层级结构中单个对象与组合对象操作不一致”的问题,与装饰器模式的“增强功能”、享元模式的“共享复用”有本质区别。

2.2 组合模式的两种实现方式(必懂,避免踩坑)

  • 透明模式:抽象构件中声明所有方法(包括添加、删除子节点),叶子节点和组合节点都实现这些方法,叶子节点的添加/删除方法可以直接抛出异常(不支持该操作)。优点是客户端访问更统一,缺点是叶子节点存在多余的方法(不符合单一职责);

  • 安全模式:抽象构件中只声明公共方法(如获取名称、统计数量),添加、删除子节点的方法只在组合节点中声明。优点是符合单一职责,缺点是客户端需要区分叶子节点和组合节点,丧失了访问的一致性。

实际开发中,透明模式更常用(优先保证客户端访问一致性,简化代码),本文所有案例均采用透明模式实现。

三、组合模式入门实现(附可复制代码,新手必练)

以“企业部门管理”为案例,模拟真实场景中“部门-员工”的树形层级结构(总部→技术部→后端小组→员工),用组合模式实现统一管理,对比“普通实现”和“组合模式实现”的差异,一看就懂、一练就会。

3.1 普通实现:层级管理的反例(痛点凸显)

企业部门管理中,部门可以包含子部门和员工,员工没有子节点,若单独定义部门类和员工类,客户端需要区分两者,逐个处理层级关系,代码繁琐、扩展性差。

// 部门类(组合对象)
public class Department {
    private String name; // 部门名称
    private List<Department> subDepartments = new ArrayList<>(); // 子部门
    private List<Employee> employees = new ArrayList<>(); // 员工

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

    // 添加子部门
    public void addSubDepartment(Department department) {
        subDepartments.add(department);
    }

    // 添加员工
    public void addEmployee(Employee employee) {
        employees.add(employee);
    }

    // 统计部门总人数(部门+子部门+员工)
    public int countTotalPeople() {
        int count = employees.size();
        // 递归统计子部门人数
        for (Department sub : subDepartments) {
            count += sub.countTotalPeople();
        }
        return count;
    }

    // 展示部门结构
    public void showStructure(int level) {
        // 打印层级缩进
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < level; i++) {
            indent.append("--");
        }
        System.out.println(indent + name + "(部门)");
        // 展示子部门
        for (Department sub : subDepartments) {
            sub.showStructure(level + 1);
        }
        // 展示员工
        for (Employee emp : employees) {
            emp.showStructure(level + 1);
        }
    }

    // getter
    public String getName() {
        return name;
    }
}

// 员工类(叶子对象)
public class Employee {
    private String name; // 员工姓名

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

    // 展示员工信息(需要和部门的showStructure方法保持一致,手动适配)
    public void showStructure(int level) {
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < level; i++) {
            indent.append("--");
        }
        System.out.println(indent + name + "(员工)");
    }

    // 员工没有子节点,无法添加子部门/员工(无对应方法)
    // getter
    public String getName() {
        return name;
    }
}

// 客户端调用(普通实现)
public class Client {
    public static void main(String[] args) {
        // 创建总部(顶级部门)
        Department headOffice = new Department("公司总部");
        // 创建技术部(子部门)
        Department techDept = new Department("技术部");
        // 创建后端小组(子部门的子部门)
        Department backendGroup = new Department("后端小组");
        // 创建员工
        Employee emp1 = new Employee("张三");
        Employee emp2 = new Employee("李四");

        // 组装层级结构(繁琐,需要区分部门和员工)
        headOffice.addSubDepartment(techDept);
        techDept.addSubDepartment(backendGroup);
        backendGroup.addEmployee(emp1);
        backendGroup.addEmployee(emp2);

        // 展示结构(需要调用部门的showStructure方法)
        System.out.println("公司组织架构:");
        headOffice.showStructure(0);

        // 统计总人数(需要调用部门的countTotalPeople方法)
        System.out.println("公司总人数:" + headOffice.countTotalPeople());

        // 痛点:若要添加新的节点类型(如实习生),需新增类,且客户端需修改代码适配
    }
}

【运行结果】:

公司组织架构:
公司总部(部门)
--技术部(部门)
----后端小组(部门)
------张三(员工)
------李四(员工)
公司总人数:2

【缺点极其明显】:

  • 客户端需区分节点类型:必须明确判断是部门(组合对象)还是员工(叶子对象),才能调用对应的方法(如addSubDepartment仅部门有),代码繁琐;

  • 扩展性差:新增节点类型(如实习生、外包员工),需新增类,且客户端需修改代码适配,不符合“开闭原则”;

  • 代码冗余:部门和员工的showStructure方法逻辑重复,无法统一管理;

  • 层级维护复杂:若层级较多(如5级、10级),组装和遍历逻辑会极其冗长,容易出错。

3.2 组合模式实现:层级管理的优雅代码(正例)

用组合模式的透明模式,定义抽象构件(OrganizationComponent),统一部门和员工的接口,部门(组合节点)维护子节点集合,员工(叶子节点)不维护子节点,客户端通过抽象接口统一访问,无需区分节点类型。

// 1. 抽象构件角色:统一部门和员工的接口(透明模式)
public abstract class OrganizationComponent {
    protected String name; // 名称(部门/员工名称)

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

    // 公共方法:展示结构(所有节点都需实现)
    public abstract void showStructure(int level);

    // 公共方法:统计人数(所有节点都需实现)
    public abstract int countTotalPeople();

    // 透明模式:抽象类中声明添加子节点方法(叶子节点可抛出异常)
    public void add(OrganizationComponent component) {
        // 默认实现:抛出异常,子类(组合节点)可重写
        throw new UnsupportedOperationException("不支持添加子节点操作");
    }

    // 透明模式:抽象类中声明删除子节点方法(叶子节点可抛出异常)
    public void remove(OrganizationComponent component) {
        throw new UnsupportedOperationException("不支持删除子节点操作");
    }

    // getter
    public String getName() {
        return name;
    }
}

// 2. 组合构件角色:部门(可包含子节点)
public class Department extends OrganizationComponent {
    // 维护子节点集合(可包含部门或员工)
    private List<OrganizationComponent> children = new ArrayList<>();

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

    // 重写添加子节点方法
    @Override
    public void add(OrganizationComponent component) {
        children.add(component);
    }

    // 重写删除子节点方法
    @Override
    public void remove(OrganizationComponent component) {
        children.remove(component);
    }

    // 重写展示结构方法:递归展示自身和子节点
    @Override
    public void showStructure(int level) {
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < level; i++) {
            indent.append("--");
        }
        System.out.println(indent + name + "(部门)");
        // 递归展示所有子节点(无需区分是部门还是员工)
        for (OrganizationComponent child : children) {
            child.showStructure(level + 1);
        }
    }

    // 重写统计人数方法:递归统计自身子节点的总人数
    @Override
    public int countTotalPeople() {
        int count = 0;
        // 递归统计所有子节点的人数(部门会继续递归,员工返回1)
        for (OrganizationComponent child : children) {
            count += child.countTotalPeople();
        }
        return count;
    }
}

// 3. 叶子构件角色:员工(无子类)
public class Employee extends OrganizationComponent {
    public Employee(String name) {
        super(name);
    }

    // 重写展示结构方法:仅展示自身信息
    @Override
    public void showStructure(int level) {
        StringBuilder indent = new StringBuilder();
        for (int i = 0; i < level; i++) {
            indent.append("--");
        }
        System.out.println(indent + name + "(员工)");
    }

    // 重写统计人数方法:员工自身算1人
    @Override
    public int countTotalPeople() {
        return 1; // 叶子节点,人数为1
    }

    // 无需重写add/remove方法,使用父类默认实现(抛出异常)
}

// 4. 客户端调用(组合模式实现)
public class Client {
    public static void main(String[] args) {
        // 1. 创建所有节点(统一使用抽象构件类型,无需区分部门和员工)
        OrganizationComponent headOffice = new Department("公司总部");
        OrganizationComponent techDept = new Department("技术部");
        OrganizationComponent backendGroup = new Department("后端小组");
        OrganizationComponent emp1 = new Employee("张三");
        OrganizationComponent emp2 = new Employee("李四");

        // 2. 组装层级结构(统一调用add方法,无需区分节点类型)
        headOffice.add(techDept);
        techDept.add(backendGroup);
        backendGroup.add(emp1);
        backendGroup.add(emp2);

        // 3. 展示结构(统一调用showStructure方法)
        System.out.println("公司组织架构:");
        headOffice.showStructure(0);

        // 4. 统计总人数(统一调用countTotalPeople方法)
        System.out.println("公司总人数:" + headOffice.countTotalPeople());

        // 5. 新增节点(如产品部),无需修改客户端核心逻辑
        OrganizationComponent productDept = new Department("产品部");
        OrganizationComponent emp3 = new Employee("王五");
        headOffice.add(productDept);
        productDept.add(emp3);

        System.out.println("新增产品部后,组织架构:");
        headOffice.showStructure(0);
        System.out.println("新增产品部后,总人数:" + headOffice.countTotalPeople());
    }
}

【运行结果】:

公司组织架构:
公司总部(部门)
--技术部(部门)
----后端小组(部门)
------张三(员工)
------李四(员工)
公司总人数:2
新增产品部后,组织架构:
公司总部(部门)
--技术部(部门)
----后端小组(部门)
------张三(员工)
------李四(员工)
--产品部(部门)
----王五(员工)
新增产品部后,总人数:3

【代码优势极其明显】:

  • 客户端无需区分节点类型:统一使用抽象构件接口,调用add、showStructure、countTotalPeople方法,无需判断是部门还是员工,代码简洁;

  • 扩展性极强:新增节点类型(如产品部、实习生),只需新增类继承抽象构件,无需修改客户端和原有代码,符合“开闭原则”;

  • 代码简洁无冗余:统一抽象接口,避免重复逻辑,层级遍历和管理通过递归实现,无需手动处理每个层级;

  • 层级维护灵活:无论层级多少,组装和遍历逻辑一致,可轻松实现复杂树形结构的管理。

【核心总结】:组合模式的核心不是“新增功能”,而是“统一管理”——通过抽象构件统一接口,将叶子节点和组合节点纳入同一树形结构,让客户端以一致的方式访问所有节点,简化层级管理的复杂度,提升系统扩展性。

四、组合模式实战(真实业务场景,可直接复用)

结合Java后端最常见的“后台管理系统菜单导航”场景,用组合模式实现多级菜单的统一渲染、菜单权限控制,贴合真实项目开发(Spring Boot环境),代码可直接复制到项目中使用。

4.1 实战场景说明

场景:后台管理系统有三级菜单(一级菜单:系统管理、用户管理;二级菜单:系统管理→菜单管理、系统管理→角色管理;三级菜单:菜单管理→新增菜单、菜单管理→编辑菜单),要求:统一渲染所有菜单、判断菜单是否有子菜单、点击菜单触发统一的跳转逻辑,支持动态添加菜单、控制菜单显示权限。

  1. 抽象构件:菜单组件(统一菜单和子菜单的接口);

  2. 组合节点:一级菜单、二级菜单(可包含子菜单);

  3. 叶子节点:三级菜单(无子类,点击可跳转);

  4. 要求:统一渲染菜单、统一处理菜单点击事件,支持权限控制(无权限的菜单不显示),可动态添加菜单。

4.2 实战代码实现(Spring Boot环境,可直接复用)

// 1. 抽象构件角色:菜单组件(透明模式)
public abstract class MenuComponent {
    protected String menuName; // 菜单名称
    protected String url; // 菜单跳转URL(叶子节点有效)
    protected String permission; // 菜单所需权限(用于权限控制)

    public MenuComponent(String menuName, String url, String permission) {
        this.menuName = menuName;
        this.url = url;
        this.permission = permission;
    }

    // 公共方法:渲染菜单(所有菜单都需实现)
    public abstract String renderMenu();

    // 公共方法:判断是否有子菜单
    public abstract boolean hasChildren();

    // 公共方法:点击菜单(触发跳转逻辑)
    public abstract void clickMenu();

    // 透明模式:添加子菜单
    public void add(MenuComponent menu) {
        throw new UnsupportedOperationException("不支持添加子菜单");
    }

    // 透明模式:删除子菜单
    public void remove(MenuComponent menu) {
        throw new UnsupportedOperationException("不支持删除子菜单");
    }

    // 透明模式:获取子菜单列表
    public List<MenuComponent> getChildren() {
        throw new UnsupportedOperationException("不支持获取子菜单");
    }

    // getters
    public String getMenuName() {
        return menuName;
    }

    public String getPermission() {
        return permission;
    }
}

// 2. 组合构件角色:父菜单(一级、二级菜单,可包含子菜单)
public class ParentMenu extends MenuComponent {
    // 维护子菜单列表
    private List<MenuComponent> children = new ArrayList<>();

    public ParentMenu(String menuName, String url, String permission) {
        super(menuName, url, permission);
    }

    // 重写添加子菜单
    @Override
    public void add(MenuComponent menu) {
        children.add(menu);
    }

    // 重写删除子菜单
    @Override
    public void remove(MenuComponent menu) {
        children.remove(menu);
    }

    // 重写获取子菜单列表
    @Override
    public List<MenuComponent> getChildren() {
        return children;
    }

    // 重写渲染菜单:递归渲染自身和子菜单(HTML格式,适配前端)
    @Override
    public String renderMenu() {
        StringBuilder menuHtml = new StringBuilder();
        // 父菜单HTML(折叠样式)
        menuHtml.append("<li class=\"parent-menu\">");
        menuHtml.append("<span>").append(menuName).append("</span>");
        menuHtml.append("<ul class=\"sub-menu\">");
        // 递归渲染子菜单
        for (MenuComponent child : children) {
            menuHtml.append(child.renderMenu());
        }
        menuHtml.append("</ul></li>");
        return menuHtml.toString();
    }

    // 重写判断是否有子菜单
    @Override
    public boolean hasChildren() {
        return !children.isEmpty();
    }

    // 重写点击菜单:父菜单点击折叠/展开子菜单
    @Override
    public void clickMenu() {
        System.out.println("点击父菜单【" + menuName + "】,展开/折叠子菜单");
    }
}

// 3. 叶子构件角色:子菜单(三级菜单,无子类)
public class SubMenu extends MenuComponent {
    public SubMenu(String menuName, String url, String permission) {
        super(menuName, url, permission);
    }

    // 重写渲染菜单:叶子菜单HTML(可点击跳转)
    @Override
    public String renderMenu() {
        return "<li class=\"sub-menu-item\"><a href=\"" + url + "\">" + menuName + "</a></li>";
    }

    // 重写判断是否有子菜单:叶子菜单无子类
    @Override
    public boolean hasChildren() {
        return false;
    }

    // 重写点击菜单:跳转至指定URL
    @Override
    public void clickMenu() {
        System.out.println("点击子菜单【" + menuName + "】,跳转至:" + url);
    }
}

// 4. 菜单服务(Spring Bean,管理菜单树形结构,处理权限控制)
@Service
public class MenuService {
    // 根菜单(顶级菜单容器)
    private final MenuComponent rootMenu = new ParentMenu("系统菜单", "#", "ROOT");

    // 初始化菜单(项目启动时执行)
    @PostConstruct
    public void initMenu() {
        // 1. 创建一级菜单:系统管理
        MenuComponent systemManage = new ParentMenu("系统管理", "#", "SYSTEM_MANAGE");
        // 2. 创建一级菜单:用户管理
        MenuComponent userManage = new ParentMenu("用户管理", "#", "USER_MANAGE");

        // 3. 创建系统管理的二级菜单:菜单管理、角色管理
        MenuComponent menuManage = new ParentMenu("菜单管理", "#", "MENU_MANAGE");
        MenuComponent roleManage = new SubMenu("角色管理", "/system/role", "ROLE_MANAGE");

        // 4. 创建菜单管理的三级菜单:新增菜单、编辑菜单
        MenuComponent addMenu = new SubMenu("新增菜单", "/system/menu/add", "MENU_ADD");
        MenuComponent editMenu = new SubMenu("编辑菜单", "/system/menu/edit", "MENU_EDIT");

        // 5. 组装菜单结构
        rootMenu.add(systemManage);
        rootMenu.add(userManage);
        systemManage.add(menuManage);
        systemManage.add(roleManage);
        menuManage.add(addMenu);
        menuManage.add(editMenu);
    }

    // 渲染菜单(根据用户权限过滤)
    public String renderMenuByPermission(List<String> userPermissions) {
        // 递归过滤有权限的菜单
        return renderMenuRecursive(rootMenu, userPermissions);
    }

    // 递归过滤菜单(权限控制核心)
    private String renderMenuRecursive(MenuComponent menu, List<String> userPermissions) {
        // 无权限,不渲染
        if (!userPermissions.contains(menu.getPermission())) {
            return "";
        }
        // 叶子菜单,直接渲染
        if (!menu.hasChildren()) {
            return menu.renderMenu();
        }
        // 父菜单,递归渲染子菜单
        ParentMenu parentMenu = (ParentMenu) menu;
        StringBuilder menuHtml = new StringBuilder();
        menuHtml.append("<li class=\"parent-menu\">");
        menuHtml.append("<span>").append(menu.getMenuName()).append("</span>");
        menuHtml.append("<ul class=\"sub-menu\">");
        for (MenuComponent child : parentMenu.getChildren()) {
            String childHtml = renderMenuRecursive(child, userPermissions);
            if (!childHtml.isEmpty()) {
                menuHtml.append(childHtml);
            }
        }
        menuHtml.append("</ul></li>");
        // 若父菜单无有权限的子菜单,不渲染父菜单
        return menuHtml.toString().contains("sub-menu-item") ? menuHtml.toString() : "";
    }

    // 动态添加菜单
    public void addMenu(MenuComponent parentMenu, MenuComponent newMenu) {
        parentMenu.add(newMenu);
        System.out.println("成功添加菜单【" + newMenu.getMenuName() + "】到【" + parentMenu.getMenuName() + "】下");
    }

    // 模拟菜单点击
    public void clickMenu(MenuComponent menu) {
        menu.clickMenu();
    }
}

// 5. 测试类(模拟业务调用,Spring Boot环境可直接注入测试)
public class MenuTest {
    public static void main(String[] args) {
        // 模拟Spring容器注入
        MenuService menuService = new MenuService();
        menuService.initMenu();

        // 模拟用户权限(拥有系统管理、菜单管理、新增菜单权限)
        List<String> userPermissions = Arrays.asList("ROOT", "SYSTEM_MANAGE", "MENU_MANAGE", "MENU_ADD");

        // 渲染菜单(根据权限过滤)
        String menuHtml = menuService.renderMenuByPermission(userPermissions);
        System.out.println("根据权限渲染的菜单HTML:");
        System.out.println(menuHtml);

        // 模拟点击菜单
        MenuComponent addMenu = new SubMenu("新增菜单", "/system/menu/add", "MENU_ADD");
        menuService.clickMenu(addMenu);

        // 动态添加菜单(新增菜单管理→删除菜单)
        MenuComponent menuManage = new ParentMenu("菜单管理", "#", "MENU_MANAGE");
        MenuComponent deleteMenu = new SubMenu("删除菜单", "/system/menu/delete", "MENU_DELETE");
        menuService.addMenu(menuManage, deleteMenu);
    }
}

【运行结果】:

根据权限渲染的菜单HTML:
<li class="parent-menu"><span>系统菜单</span><ul class="sub-menu"><li class="parent-menu"><span>系统管理</span><ul class="sub-menu"><li class="parent-menu"><span>菜单管理</span><ul class="sub-menu"><li class="sub-menu-item"><a href="/system/menu/add">新增菜单</a></li></ul></li></ul></li></ul></li>
点击子菜单【新增菜单】,跳转至:/system/menu/add
成功添加菜单【删除菜单】到【菜单管理】下

【实战亮点】:

  • 贴合Spring Boot实战:使用@Service、@PostConstruct注解,符合真实项目开发规范,可直接复制复用;

  • 权限控制完善:通过递归过滤菜单权限,无权限的菜单不渲染,贴合后台管理系统的实际需求;

  • 扩展性极强:新增菜单类型(如四级菜单、特殊菜单),只需新增类继承MenuComponent,无需修改原有代码;

  • 前后端适配:渲染的菜单为HTML格式,可直接对接前端页面,点击事件统一处理,降低前后端联调成本;

  • 动态可配置:支持动态添加、删除菜单,无需重启项目,适配实际项目中的菜单动态管理需求。

补充:真实项目中,菜单数据通常从数据库查询,可在initMenu方法中查询数据库,动态组装菜单树形结构,进一步提升实用性。

五、组合模式在JDK/框架中的应用(面试必提)

组合模式的核心价值是“统一管理树形结构”,这也是它被广泛应用在JDK源码和主流Java框架中的原因,掌握这些应用场景,面试时能加分不少,还能帮助你理解框架底层设计思想。

5.1 JDK 中的组合模式(最常见,面试高频)

JDK中有多个经典的组合模式应用,其中集合框架、XML解析是面试必问考点,一定要掌握:

5.1.1 Java 集合框架(最典型)

Java集合框架中的Container(容器)和Leaf(叶子)结构,是组合模式的标准实现,最典型的就是List和ArrayList、Set和HashSet:

  • 抽象构件(Component):Collection接口(定义了所有容器的公共方法,如add、remove、size、iterator);

  • 组合构件(Composite):ArrayList、HashSet、HashMap(可包含多个元素,相当于“组合节点”,维护元素集合,实现Collection的所有方法);

  • 叶子构件(Leaf):集合中的元素(如String、Integer,相当于“叶子节点”,不包含子元素,无法添加/删除其他元素)。

public class CollectionCompositeDemo {
    public static void main(String[] args) {
        // 组合节点:ArrayList(可包含多个叶子节点)
        Collection<String> list = new ArrayList<>();
        // 叶子节点:String对象(无子类)
        String str1 = "组合模式";
        String str2 = "Java设计模式";

        // 统一调用add方法(客户端无需区分组合和叶子)
        list.add(str1);
        list.add(str2);

        // 统一调用iterator方法,遍历所有元素(组合节点递归遍历叶子节点)
        for (String str : list) {
            System.out.println(str);
        }

        // 统一调用size方法,统计元素数量(组合节点统计所有叶子节点数量)
        System.out.println("集合大小:" + list.size());
    }
}

核心逻辑:Collection接口作为抽象构件,统一了容器(组合节点)和元素(叶子节点)的访问方式,客户端无需区分是容器还是元素,通过Collection接口即可完成添加、删除、遍历、统计等操作,这是组合模式透明模式的典型应用。

5.1.2 XML 解析(DOM 模型)

XML解析中的DOM(文档对象模型),将XML文档解析为树形结构,每个节点(元素节点、文本节点、属性节点)都统一继承自Node接口,是组合模式的经典应用:

  • 抽象构件(Component):Node接口(定义了所有节点的公共方法,如appendChild、removeChild、getNodeName);

  • 组合构件(Composite):Element节点(元素节点,可包含子节点,如&lt;user&gt;标签可包含&lt;name&gt;、&lt;age&gt;子标签);

  • 叶子构件(Leaf):Text节点、Attribute节点(文本节点和属性节点,无子类,如&lt;name&gt;张三&lt;/name&gt;中的“张三”是Text节点)。

核心逻辑:客户端通过Node接口,统一访问所有XML节点,无需区分是元素节点(组合)还是文本节点(叶子),可轻松实现XML文档的遍历、新增、删除节点等操作。

5.2 框架中的组合模式

5.2.1 Spring MVC 中的组合模式

Spring MVC的HandlerMapping(处理器映射器),用于管理请求路径与处理器的映射关系,底层采用组合模式实现:

  • 抽象构件(Component):HandlerMapping接口(定义了获取处理器的公共方法getHandler);

  • 组合构件(Composite):CompositeHandlerMapping(组合处理器映射器,可包含多个子HandlerMapping);

  • 叶子构件(Leaf):RequestMappingHandlerMapping、BeanNameUrlHandlerMapping(具体的处理器映射器,无子类,负责具体的请求路径映射)。

核心逻辑:CompositeHandlerMapping作为组合节点,管理多个子HandlerMapping,客户端通过HandlerMapping接口统一获取处理器,无需区分是组合映射器还是具体映射器,简化了请求路径的管理和扩展。

5.2.2 MyBatis 中的组合模式

MyBatis的SQL语句构建器(SqlNode),用于动态构建SQL语句,底层采用组合模式实现:

  • 抽象构件(Component):SqlNode接口(定义了构建SQL的公共方法apply);

  • 组合构件(Composite):MixedSqlNode(组合SqlNode,可包含多个子SqlNode,如if、where、foreach标签组合);

  • 叶子构件(Leaf):TextSqlNode、StaticTextSqlNode(具体的SqlNode,无子类,负责拼接静态SQL文本)。

核心逻辑:通过组合模式,将多个SqlNode组合成复杂的SQL语句,客户端统一调用apply方法,无需区分是组合SqlNode还是叶子SqlNode,实现了SQL语句的动态构建和灵活扩展。

六、组合模式面试高频考点(必背,避坑)

组合模式是Java后端面试的高频考点(中高级岗位尤为突出),重点考察“核心思想”“核心角色”“JDK应用”“与其他模式的区别”,记住以下考点,轻松应对面试。

1. 组合模式的核心作用是什么?(高频)

核心答案(一句话记住,面试直接说):将对象组合成树形结构,表示“部分-整体”的层次关系,统一单个对象和组合对象的访问接口,让客户端无需区分两者,简化层级管理,提升系统扩展性

补充:组合模式解决的是“层级结构中单个对象与组合对象操作不一致”的问题,核心是“统一访问”和“树形管理”,而非“功能增强”或“共享复用”。

2. 组合模式的核心角色有哪些?(必背)

核心答案(一句话记住):抽象构件(统一接口)、叶子构件(单个对象,无子类)、组合构件(组合对象,可包含子节点)

角色名称 核心职责 示例
抽象构件 定义所有节点的公共接口,声明公共方法,保证客户端访问一致性 OrganizationComponent、Collection
叶子构件 表示单个对象,无子类,实现抽象构件的公共方法,不支持添加/删除子节点 Employee、SubMenu、String
组合构件 表示组合对象,可包含子节点,实现抽象构件的公共方法,提供添加/删除子节点的功能 Department、ParentMenu、ArrayList

3. 组合模式的透明模式和安全模式有什么区别?(高频)

核心区别(一句话区分):透明模式在抽象构件中声明所有方法(包括添加/删除子节点),客户端访问更统一;安全模式仅在组合构件中声明添加/删除子节点方法,符合单一职责,但客户端需区分节点类型

对比维度 透明模式 安全模式
方法声明位置 抽象构件中声明所有方法(包括add/remove) 抽象构件声明公共方法,add/remove仅在组合构件中声明
客户端访问 无需区分节点类型,统一访问,简洁 需区分节点类型,否则无法调用add/remove,繁琐
设计原则 违背单一职责(叶子节点有多余方法) 符合单一职责(叶子节点仅实现自身所需方法)
实际应用 更常用,优先保证客户端访问一致性 较少用,仅在对单一职责要求极高的场景使用

4. 组合模式和装饰器模式的区别?(高频中的高频)

很多面试官会把这两个模式放在一起问,核心区别(一句话区分):组合模式核心是“树形结构管理”,关注“部分-整体”的层级关系;装饰器模式核心是“功能增强”,关注给对象动态添加额外功能,无层级关系

对比维度 组合模式 装饰器模式
核心目的 管理树形结构,统一单个与组合对象的访问 动态给对象添加额外功能,不改变对象结构
结构关系 树形结构,存在“部分-整体”的层级关系,节点可嵌套 链式结构,无层级关系,装饰器与被装饰者是“包装”关系
对象关系 组合对象包含多个子节点(叶子/组合),是“包含”关系 装饰器包裹被装饰者,是“关联”关系,无包含关系
使用场景 部门架构、菜单导航、文件目录等树形结构管理 IO流增强、日志增强等动态给对象添加功能的场景
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐