11、Java 组合模式从入门到实战
Java 组合模式从入门到实战(后端必看,附案例+面试考点)
前言:组合模式(Composite Pattern)是Java设计模式中最实用的“树形结构管理神器”之一,属于结构型模式,核心是“将对象组合成树形结构,以表示‘部分-整体’的层次关系,让客户端对单个对象和组合对象的访问具有一致性”。
很多Java后端开发者在面对树形结构场景(如部门组织架构、菜单导航、文件目录)时,容易写出冗长、繁琐的嵌套代码,难以维护和扩展;面试时被问到“组合模式的核心是什么”“JDK中哪里用到了组合模式”“组合模式和装饰器模式的区别”,常常无从下手。
本文从入门到实战,用极简语言拆解组合模式核心,结合可直接复制运行的代码案例、真实业务场景(部门架构、菜单管理),以及高频面试考点,带你吃透组合模式,新手也能快速上手,看完就能落地到项目中。
一、为什么Java后端必须掌握组合模式?(痛点直击)
先看3个Java后端开发中最常见的树形结构场景,你一定遇到过,这也是组合模式的核心应用场景:
-
场景1:企业部门管理系统中,公司有总部、分公司、部门、小组,形成层级树形结构(总部→分公司→部门→小组),需要统一查询某个节点下的所有子节点、统计部门人数,若逐个处理单个节点和组合节点,代码会极其繁琐;
-
场景2:后台管理系统的菜单导航,有一级菜单、二级菜单、三级菜单,需要统一渲染所有菜单、判断菜单是否有子菜单、点击菜单触发统一逻辑,若单独处理不同层级的菜单,扩展性极差;
-
场景3:面试时,面试官追问“Spring MVC的HandlerMapping底层如何管理请求路径”“JDK的集合框架中哪些类用到了组合模式”,其实这些都是组合模式的典型应用,答不上来就会错失加分机会。
而组合模式的核心价值,就是通过树形结构统一管理“单个对象”和“组合对象”,让客户端无需区分两者,采用一致的方式访问,从而简化代码、提升扩展性。
简单说,就是“单个和组合,待遇都一样”——就像公司的组织架构,无论是单个员工(叶子节点),还是整个部门(组合节点),都可以统一统计人数、统一管理,无需单独处理。
核心结论:组合模式不是“花里胡哨”的设计,而是后端开发的“树形结构刚需”——初级开发者用它简化树形结构的操作逻辑,中级开发者用它设计可扩展的层级架构,高级开发者用它理解框架底层的树形管理逻辑(如Spring MVC、MyBatis),面试时更是高频考点(尤其是中高级岗位)。
二、组合模式核心概念(极简入门,无需死记硬背)
组合模式的本质很简单:将“单个对象”(叶子节点)和“组合对象”(容器节点)统一继承自同一个抽象类/接口,组合对象内部维护一个子节点集合,从而形成树形结构,客户端通过抽象接口访问所有节点,无需区分类型。
就像部门架构:部门(组合节点)可以包含子部门(组合节点)和员工(叶子节点),无论是部门还是员工,都有“名称”“编号”,都能执行“统计人数”的操作,客户端无需区分是部门还是员工,直接调用统一方法即可。
2.1 核心角色(3个核心,必记,区分叶子和容器是关键)
-
抽象构件角色(Component):定义所有节点(叶子节点和组合节点)的统一接口,声明公共方法(如获取名称、统计数量、添加/删除子节点),是组合模式的核心,确保客户端访问的一致性;
-
叶子构件角色(Leaf):表示树形结构中的“单个对象”,没有子节点,实现抽象构件的公共方法,是树形结构的最底层节点(如员工、单个菜单、单个文件);
-
组合构件角色(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 实战场景说明
场景:后台管理系统有三级菜单(一级菜单:系统管理、用户管理;二级菜单:系统管理→菜单管理、系统管理→角色管理;三级菜单:菜单管理→新增菜单、菜单管理→编辑菜单),要求:统一渲染所有菜单、判断菜单是否有子菜单、点击菜单触发统一的跳转逻辑,支持动态添加菜单、控制菜单显示权限。
-
抽象构件:菜单组件(统一菜单和子菜单的接口);
-
组合节点:一级菜单、二级菜单(可包含子菜单);
-
叶子节点:三级菜单(无子类,点击可跳转);
-
要求:统一渲染菜单、统一处理菜单点击事件,支持权限控制(无权限的菜单不显示),可动态添加菜单。
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节点(元素节点,可包含子节点,如<user>标签可包含<name>、<age>子标签);
-
叶子构件(Leaf):Text节点、Attribute节点(文本节点和属性节点,无子类,如<name>张三</name>中的“张三”是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流增强、日志增强等动态给对象添加功能的场景 |
更多推荐


所有评论(0)