组合 (Composite) 是一种结构型设计模式,用来把对象组织成树形结构,让客户端可以用同一套接口去操作“单个对象”和“由很多对象组成的整体”。
直观一点的理解就是:不管是叶子节点,还是一整棵子树,在客户端眼里都是同一种“组件”。
- 业务里存在典型的“部分-整体”层次结构:比如学院下面有专业,专业下面有班级,班级下面有学生。
- 如果客户端每次都要判断“这是学院还是班级还是学生”,写一堆
if/else来分别处理,代码会越来越乱、越来越难扩展。 - 希望可以用统一的抽象来描述“任何层级的节点”,让客户端直接对这类抽象进行遍历、统计等操作,而不用在意这是叶子还是中间节点。
- 抽象出一个统一的组件 (Component):描述“树里的一个节点”,无论是叶子还是中间节点,都要实现它。
- 叶子节点 (Leaf):没有子节点,只关注自身的业务语义,比如“某个学生”“某个课程”。
- 组合节点 (Composite):内部维护子组件列表,实现业务时通常是“遍历子组件并委托给它们”,同时提供
add/remove等管理子节点的方法。 - 客户端只需要面向
Component编程,可以对整棵树做统一处理,而不必关心当前拿到的是哪一层。
classDiagram
class Component {
<<abstract>>
+operation()* void
+add(Component c) void
+remove(Component c) void
}
class Leaf {
+operation() void
}
class Composite {
-children List~Component~
+operation() void
+add(Component c) void
+remove(Component c) void
}
Component <|-- Leaf
Component <|-- Composite
Composite o-- Component : children
示例:学校组织架构的组合模型
Section titled “示例:学校组织架构的组合模型”在一个高校信息系统里,经常会有类似这样的层级:
- 学校
- 学院
- 专业
- 班级
- 学生
- 班级
- 专业
- 学院
很多后台服务都需要在这棵树上做操作,比如:
- 统计某个学院下所有学生数量。
- 列出某个专业下所有班级和学生。
- 统一打印整个组织架构树。
如果在客户端层面写一堆“如果是学院就遍历专业,如果是班级就遍历学生”,会非常杂乱。用组合模式可以把这些节点统一抽象为 OrgComponent。
1. 组件抽象:统一的组织节点
Section titled “1. 组件抽象:统一的组织节点”/** * 组件抽象:组织树中的一个节点 * 可以是学校、学院、专业、班级、学生等 */public abstract class OrgComponent {
protected String name;
public OrgComponent(String name) { this.name = name; }
/** * 展示当前节点信息(以及可能的子节点) */ public abstract void print(int level);
/** * 统计当前节点下的学生数量 */ public abstract int countStudents();
/** * 组合节点会重写,叶子节点默认不支持 */ public void add(OrgComponent child) { throw new UnsupportedOperationException(); }
public void remove(OrgComponent child) { throw new UnsupportedOperationException(); }}2. 叶子节点:学生
Section titled “2. 叶子节点:学生”/** * 叶子节点:学生 */public class StudentLeaf extends OrgComponent {
private final String studentId;
public StudentLeaf(String name, String studentId) { super(name); this.studentId = studentId; }
@Override public void print(int level) { String indent = " ".repeat(level); System.out.println(indent + "学生: " + name + "(学号 " + studentId + ")"); }
@Override public int countStudents() { return 1; }}3. 组合节点:学院 / 专业 / 班级
Section titled “3. 组合节点:学院 / 专业 / 班级”import java.util.ArrayList;import java.util.List;
/** * 组合节点:可以包含子组织或学生 */public class OrgComposite extends OrgComponent {
private final List<OrgComponent> children = new ArrayList<>(); private final String type; // 学校 / 学院 / 专业 / 班级
public OrgComposite(String name, String type) { super(name); this.type = type; }
@Override public void print(int level) { String indent = " ".repeat(level); System.out.println(indent + type + ": " + name); for (OrgComponent child : children) { child.print(level + 1); } }
@Override public int countStudents() { int total = 0; for (OrgComponent child : children) { total += child.countStudents(); } return total; }
@Override public void add(OrgComponent child) { children.add(child); }
@Override public void remove(OrgComponent child) { children.remove(child); }}4. 客户端:统一处理整棵组织树
Section titled “4. 客户端:统一处理整棵组织树”/** * 客户端:只依赖 OrgComponent 抽象 */public class OrgClient {
public static void main(String[] args) { // 学校 OrgComposite university = new OrgComposite("示例大学", "学校");
// 学院 OrgComposite csCollege = new OrgComposite("计算机学院", "学院"); OrgComposite mathCollege = new OrgComposite("数学学院", "学院");
// 专业 OrgComposite csMajor = new OrgComposite("软件工程", "专业"); OrgComposite aiMajor = new OrgComposite("人工智能", "专业");
// 班级 OrgComposite seClass1 = new OrgComposite("软工 2301 班", "班级");
// 学生 seClass1.add(new StudentLeaf("张三", "20230001")); seClass1.add(new StudentLeaf("李四", "20230002"));
csMajor.add(seClass1); csCollege.add(csMajor); csCollege.add(aiMajor);
university.add(csCollege); university.add(mathCollege);
// 统一打印组织树 university.print(0);
// 统计整个学校的学生数量 int totalStudents = university.countStudents(); System.out.println("全校学生总数: " + totalStudents); }}在这个示例中:
OrgComponent抽象了树中的节点,不关心具体层级。OrgComposite代表学校、学院、专业、班级等可以继续往下挂子节点的组织单元。StudentLeaf是叶子节点,表示具体学生。- 客户端只依赖
OrgComponent,就可以统一地打印整棵树、统计学生数量,而不用写很多层级判断。
- 学校组织结构、课程目录、权限菜单等天然是树形结构的场景。
- 需要对“整体”和“部分”做统一操作,例如批量授权、批量统计、统一导出等。
- 希望避免在客户端代码里出现大量“如果是叶子就这样,如果是组合就那样”的分支判断。
优点与注意事项
Section titled “优点与注意事项”优点
- 统一抽象:客户端只依赖一个组件抽象即可处理单个对象和组合对象。
- 易于扩展:新增一种叶子或组合节点类型,只需实现组件抽象接口,不影响已有客户端逻辑。
- 天然适配树结构:对于学校这种多层级组织、课程分类等树形数据,模型自然、表达清晰。
注意事项
- 组合根节点的职责要控制好,避免把过多业务逻辑都塞进组合节点导致膨胀。
- 不是所有结构都要强行抽象成组合模式,简单结构直接用列表也许更直观。
- 组合模式解决的是“部分-整体”场景下,如何用统一方式处理叶子和组合节点的问题。
- 通过统一的
Component抽象和树形结构,客户端可以在不知道具体层级的情况下,对整棵树执行遍历、统计等操作。 - 在学校的组织架构、课程目录、权限菜单等场景中,组合模式都非常适合用来建模和实现后台服务逻辑。