【软考设计模式】组合模式:“部分-整体“层次结构与树形对象统一操作精讲

系列定位:软考软件设计师 / 系统架构设计师 --- 结构型模式专题第 7 讲(结构型模式补完篇)

考察分值:上午题 1-2 分,下午题常作为代码填空或类图识别出现

难度等级:⭐⭐⭐☆☆(树形结构直观,递归遍历是填空重点)


一、考纲定位与模式定义

1.1 考纲要求

组合模式在软考中属于 结构型模式 的基础内容。考察形式包括:

  • 上午选择题:判断场景描述所属模式;识别组合模式与装饰模式、外观模式的区别;判断类图中是否存在 "部分-整体" 树形结构;区分 Leaf(叶子)与 Composite(组合)的角色

  • 下午设计题 :补全 Composite 类的 add() / remove() 方法(维护子组件列表);补全 operation() 方法中遍历子组件并递归调用的逻辑;识别类图中 Component、Leaf、Composite 三个角色

1.2 模式定义

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

核心意图 :当系统中存在 树形结构 (如文件系统、组织架构、菜单树、部门层级),且客户端需要 统一地处理 单个节点(Leaf)和组合节点(Composite)时,组合模式让两者实现同一个接口(Component),客户端无需区分 "这是一个文件还是文件夹"、"这是一个员工还是部门",统一调用 operation() 即可。

通俗理解

公司的组织架构是一棵树。CEO 是根节点(Composite),下面有技术部、市场部等部门(Composite),部门下面有具体的员工(Leaf)。如果公司要发一封全员邮件,CEO 把邮件发给各部门,各部门再发给下属员工。对发邮件这个操作来说,部门员工 都支持 "接收邮件",只是部门的实现是转发给下属,员工的实现是自己阅读。组合模式让 "发邮件" 这个操作对部门和员工 一视同仁


二、UML 类图与角色划分

复制代码
┌────────────────────────────────────────────────────────────┐
│                      Client                                │
│  + main()                                                  │
│    ├── Component leaf = new Leaf();                        │
│    └── leaf.operation();                                   │
│    ├── Component composite = new Composite();              │
│    ├── composite.add(leaf);                                │
│    └── composite.operation();  // 递归调用子组件的 operation  │
└───────────────────────┬────────────────────────────────────┘
                        │ uses uniformly
                        ▼
┌─────────────────────────────────────────────────────────┐
│                  <<interface>>                          │
│                   Component                             │
│  + operation()                                          │
│  + add(Component c)        ◄── 可选,Leaf 中可能空实现     │
│  + remove(Component c)    ◄── 可选,Leaf 中可能空实现      │
│  + getChild(int i): Component                           │
└───────────────────────┬─────────────────────────────────┘
           ┌────────────┴────────────┐
           ▼                         ▼
┌─────────────────────┐   ┌────────────────────────┐
│        Leaf         │   │     Composite          │
│  (叶子节点:无子节点)  │   │  (组合节点:有子节点)     │
│  + operation() {    │   │  - children: List      │
│      // 执行具体操作  │   │    <Component>         │
│    }                │   │  + add(Component) {    │
│  + add(c) {         │   │      children.add(c)   │
│      // 空实现或抛异常│   │    }                   │
│    }                │   │  + remove(Component){  │
│  + remove(c) {      │   │      children.remove(c)│
│      // 空实现或抛异常│   │    }                   │
│    }                │   │  + operation() {       │
│                     │   │      // 遍历子组件       │
│                     │   │      for (c:children)  │
│                     │   │        c.operation();  │
│                     │   │    }                   │
└─────────────────────┘   └────────────────────────┘
角色 职责 软考填空关键词
Component(抽象组件) 定义 Leaf 和 Composite 的共同接口,声明 operation()add()remove() 等方法 interface / abstract class + operation() + add(Component)
Leaf(叶子节点) 表示树中的叶子对象,没有子节点,实现 operation() 完成具体操作;add()/remove() 通常空实现或抛异常 implements Component + operation() 具体实现
Composite(组合节点) 表示有子节点的组合对象,存储一个 Component 列表(children),实现 add()/remove() 管理子组件;operation() 中遍历子组件并递归调用 implements Component + List<Component> children + 遍历递归
Client(客户端) 面向 Component 接口编程,统一对待 Leaf 和 Composite Component c = new Leaf()new Composite()

类图识别要点 :软考类图中,组合模式的核心特征是 Composite 类内部持有 List<Component>Component[] (组合关系,一对多),且 operation() 方法中有 遍历子组件并调用 operation() 的递归逻辑。如果类图里是一个树形结构(一个节点包含多个子节点,子节点又可以是组合节点)→ 组合模式。


三、场景一:树形菜单系统(最经典的组合模式)

业务背景 :系统需要渲染一个多级菜单。菜单项有两种:① 叶子菜单项(如 "退出",点击后直接执行操作)② 子菜单(如 "文件",点击后展开下属菜单)。要求客户端统一调用 render() 方法渲染整个菜单树,无需区分叶子和子菜单。

说明:这是组合模式最经典的例子,也是软考下午题最爱考的 "树形结构统一操作" 场景。

3.1 代码实现
java 复制代码
import java.util.ArrayList;
import java.util.List;

// Component:菜单组件接口
public interface MenuComponent {
    void render();
    void add(MenuComponent component);
    void remove(MenuComponent component);
    MenuComponent getChild(int index);
}

// Leaf:叶子菜单项(无子节点)
public class MenuItem implements MenuComponent {
    private String name;
    private String action;
    
    public MenuItem(String name, String action) {
        this.name = name;
        this.action = action;
    }
    
    @Override
    public void render() {
        System.out.println("  菜单项: " + name + " [操作: " + action + "]");
    }
    
    @Override
    public void add(MenuComponent component) {
        throw new UnsupportedOperationException("叶子节点不能添加子组件");
    }
    
    @Override
    public void remove(MenuComponent component) {
        throw new UnsupportedOperationException("叶子节点不能移除子组件");
    }
    
    @Override
    public MenuComponent getChild(int index) {
        throw new UnsupportedOperationException("叶子节点没有子组件");
    }
}

// Composite:子菜单(有子节点)
public class SubMenu implements MenuComponent {
    private String name;
    private List<MenuComponent> children = new ArrayList<>();
    
    public SubMenu(String name) {
        this.name = name;
    }
    
    @Override
    public void add(MenuComponent component) {
        children.add(component);
    }
    
    @Override
    public void remove(MenuComponent component) {
        children.remove(component);
    }
    
    @Override
    public MenuComponent getChild(int index) {
        return children.get(index);
    }
    
    @Override
    public void render() {
        System.out.println("子菜单: " + name + " {");
        for (MenuComponent child : children) {
            child.render();
        }
        System.out.println("}");
    }
}

// Client:客户端统一渲染
public class Client {
    public static void main(String[] args) {
        MenuComponent rootMenu = new SubMenu("系统菜单");
        
        MenuComponent fileMenu = new SubMenu("文件");
        fileMenu.add(new MenuItem("新建", "create"));
        fileMenu.add(new MenuItem("打开", "open"));
        fileMenu.add(new MenuItem("保存", "save"));
        
        MenuComponent editMenu = new SubMenu("编辑");
        editMenu.add(new MenuItem("撤销", "undo"));
        editMenu.add(new MenuItem("重做", "redo"));
        
        MenuComponent prefMenu = new SubMenu("首选项");
        prefMenu.add(new MenuItem("界面设置", "ui"));
        prefMenu.add(new MenuItem("快捷键", "shortcut"));
        editMenu.add(prefMenu);
        
        rootMenu.add(fileMenu);
        rootMenu.add(editMenu);
        rootMenu.add(new MenuItem("退出", "exit"));
        
        rootMenu.render();
    }
}

运行结果

复制代码
子菜单: 系统菜单 {
子菜单: 文件 {
  菜单项: 新建 [操作: create]
  菜单项: 打开 [操作: open]
  菜单项: 保存 [操作: save]
}
子菜单: 编辑 {
  菜单项: 撤销 [操作: undo]
  菜单项: 重做 [操作: redo]
子菜单: 首选项 {
  菜单项: 界面设置 [操作: ui]
  菜单项: 快捷键 [操作: shortcut]
}
}
  菜单项: 退出 [操作: exit]
}

关键体会SubMenu.render() 中通过 for-each 遍历 children 列表,对每个子组件调用 render()。如果子组件是 SubMenu,会再次进入 SubMenu.render() 递归展开;如果是 MenuItem,则直接渲染。客户端完全不需要 if (child instanceof SubMenu) 这样的类型判断。


四、场景二:文件系统(文件与文件夹统一操作)

业务背景 :文件系统中,文件(File)和文件夹(Folder)都支持 "显示大小" 操作。文件的大小就是自身字节数,文件夹的大小是内部所有文件大小的总和。客户端统一调用 getSize() 获取任何节点的大小,无需区分文件和文件夹。

说明:这是软考下午题中常见的 "递归计算" 场景,Composite 的 getSize() 需要遍历子节点并累加。

4.1 代码实现
java 复制代码
import java.util.ArrayList;
import java.util.List;

// Component:文件系统节点接口
public interface FileSystemNode {
    String getName();
    long getSize();
    void add(FileSystemNode node);
    void remove(FileSystemNode node);
}

// Leaf:文件(叶子节点)
public class File implements FileSystemNode {
    private String name;
    private long size;
    
    public File(String name, long size) {
        this.name = name;
        this.size = size;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public long getSize() {
        return size;
    }
    
    @Override
    public void add(FileSystemNode node) {
        throw new UnsupportedOperationException("文件不能添加子节点");
    }
    
    @Override
    public void remove(FileSystemNode node) {
        throw new UnsupportedOperationException("文件不能移除子节点");
    }
}

// Composite:文件夹(组合节点)
public class Folder implements FileSystemNode {
    private String name;
    private List<FileSystemNode> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public long getSize() {
        long totalSize = 0;
        for (FileSystemNode child : children) {
            totalSize += child.getSize();
        }
        return totalSize;
    }
    
    @Override
    public void add(FileSystemNode node) {
        children.add(node);
    }
    
    @Override
    public void remove(FileSystemNode node) {
        children.remove(node);
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        FileSystemNode root = new Folder("根目录");
        
        FileSystemNode docs = new Folder("文档");
        docs.add(new File("简历.pdf", 1024));
        docs.add(new File("报告.docx", 2048));
        
        FileSystemNode photos = new Folder("照片");
        photos.add(new File("旅行.jpg", 5120));
        photos.add(new File("聚会.png", 3072));
        
        root.add(docs);
        root.add(photos);
        root.add(new File("readme.txt", 100));
        
        System.out.println("根目录总大小: " + root.getSize() + " 字节");
        System.out.println("文档文件夹大小: " + docs.getSize() + " 字节");
        System.out.println("照片文件夹大小: " + photos.getSize() + " 字节");
    }
}

关键体会Folder.getSize() 通过递归累加所有子节点的 getSize()。如果子节点是 Folder,会再次递归;如果是 File,直接返回文件大小。客户端只需调用 root.getSize(),整个树的大小自动计算出来。

软考高频考点 :组合模式的 operation() 中必须有 递归遍历 子组件的逻辑。如果代码填空里 Composite 的 getSize() 只返回自身大小而没有遍历子节点,会判错。


五、场景三:组织架构与薪资计算(带返回值递归)

业务背景:公司组织架构是树形结构。员工(Employee)是叶子节点,有基本工资。部门(Department)是组合节点,包含下属员工和子部门。要求统一计算任意节点的月度薪资总额:员工的薪资是自己的工资,部门的薪资是下属所有节点薪资的总和。

说明:这是软考下午题中 "带返回值的递归计算" 场景,比 void render() 稍复杂,但核心逻辑相同。

5.1 代码实现
java 复制代码
import java.util.ArrayList;
import java.util.List;

// Component:组织节点接口
public interface OrgNode {
    String getName();
    double calculateSalary();
    void add(OrgNode node);
    void remove(OrgNode node);
}

// Leaf:员工(叶子节点)
public class Employee implements OrgNode {
    private String name;
    private double salary;
    
    public Employee(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public double calculateSalary() {
        return salary;
    }
    
    @Override
    public void add(OrgNode node) {
        throw new UnsupportedOperationException("员工不能添加下属");
    }
    
    @Override
    public void remove(OrgNode node) {
        throw new UnsupportedOperationException("员工不能移除下属");
    }
}

// Composite:部门(组合节点)
public class Department implements OrgNode {
    private String name;
    private List<OrgNode> members = new ArrayList<>();
    
    public Department(String name) {
        this.name = name;
    }
    
    @Override
    public String getName() { return name; }
    
    @Override
    public double calculateSalary() {
        double total = 0;
        for (OrgNode member : members) {
            total += member.calculateSalary();
        }
        return total;
    }
    
    @Override
    public void add(OrgNode node) {
        members.add(node);
    }
    
    @Override
    public void remove(OrgNode node) {
        members.remove(node);
    }
}

// Client
public class Client {
    public static void main(String[] args) {
        OrgNode company = new Department("总公司");
        
        OrgNode techDept = new Department("技术部");
        techDept.add(new Employee("张三", 15000));
        techDept.add(new Employee("李四", 18000));
        
        OrgNode qaTeam = new Department("测试组");
        qaTeam.add(new Employee("王五", 12000));
        qaTeam.add(new Employee("赵六", 12000));
        techDept.add(qaTeam);
        
        OrgNode marketDept = new Department("市场部");
        marketDept.add(new Employee("孙七", 10000));
        marketDept.add(new Employee("周八", 10000));
        
        company.add(techDept);
        company.add(marketDept);
        company.add(new Employee("CEO", 50000));
        
        System.out.println("公司月度薪资总额: ¥" + company.calculateSalary());
        System.out.println("技术部月度薪资总额: ¥" + techDept.calculateSalary());
        System.out.println("测试组月度薪资总额: ¥" + qaTeam.calculateSalary());
    }
}

六、三种场景对比与演进思路

维度 场景一:树形菜单 场景二:文件系统 场景三:组织架构
Component MenuComponent FileSystemNode OrgNode
Leaf MenuItem(菜单项) File(文件) Employee(员工)
Composite SubMenu(子菜单) Folder(文件夹) Department(部门)
operation() render()(渲染) getSize()(获取大小) calculateSalary()(计算薪资)
返回值 void long double
递归逻辑 遍历子组件调用 render() 累加子组件 getSize() 累加子组件 calculateSalary()
叶子 add/remove 抛异常 抛异常 抛异常
软考考法 下午大题(补全 render 遍历) 下午大题(补全 size 累加) 下午大题(补全薪资计算)

七、组合模式 vs 装饰模式 vs 外观模式:核心对比

对比项 组合模式 装饰模式 外观模式
设计目的 统一 树形结构 中部分和整体的操作 增强 单个对象的功能(层层包装) 简化 多个子系统的使用
结构特征 树形结构(一对多,递归组合) 链式结构(一对一,层层包装) 星形结构(一个 Facade 连接多个子系统)
关系类型 Composite 组合 多个 Component Decorator 组合 一个 Component Facade 关联 多个 Subsystem
递归方向 自顶向下遍历树(分发操作) 自外向内委托(增强操作) 无递归,顺序调用
Client 调用 component.operation()(统一对待) decorator.operation()(层层增强) facade.operation()(简化入口)
是否新增功能 ❌ 不新增(只是统一接口) ✅ 新增功能(如加料、加时间戳) ❌ 不新增(只是封装调用)
软考判断 看到 "树形结构"、"部分整体"、"统一处理文件和文件夹" → 组合 看到 "套娃"、"增强"、"添加职责" → 装饰 看到 "简化子系统"、"统一入口" → 外观

记忆口诀:组合是 "树形统一"(部分整体一视同仁),装饰是 "套娃增强"(层层加功能),外观是 "简化入口"(封装复杂子系统)。


八、软考高频考点与易混淆辨析

8.1 高频考点
考点 内容
模式分类 结构型模式(GoF 23 正式成员)
核心三角色 Component(抽象组件)、Leaf(叶子)、Composite(组合)
核心思想 将对象组合成 树形结构 ,让客户端对 单个对象组合对象 的使用具有一致性
透明性 客户端面向 Component 接口编程,无需区分 Leaf 和 Composite
Composite 的关键 内部维护 List<Component> childrenoperation() 中遍历子组件递归调用
Leaf 的 add/remove 通常 空实现抛异常UnsupportedOperationException
与装饰区别 组合:树形结构,一对多,统一处理部分和整体;装饰:链式结构,一对一,增强单个对象
与外观区别 组合:处理树形层次结构;外观:简化多个平级子系统的调用
适用场景 ① 树形菜单/目录 ② 文件系统 ③ 组织架构 ④ 图形编辑器中的组合图形
8.2 易混淆辨析:组合 vs 装饰(最难辨析)
对比项 组合模式 装饰模式
UML 结构 都有 Component 接口,都有组合关系 都有 Component 接口,都有组合关系
结构形态 树形(一个 Composite 包含多个子 Component) 链式(一个 Decorator 包装一个 Component)
子对象数量 Composite 有 多个 子对象 Decorator 只有 一个 被装饰对象
递归方向 自顶向下分发操作(父节点调用子节点) 自外向内委托操作(外层调用内层)
目的 部分和整体 被统一处理 单个对象 动态添加职责
Client 代码 root.add(child1); root.add(child2); root.operation(); c = new DecoratorA(new DecoratorB(component)); c.operation();
软考判断 看到 "树形"、"文件夹和文件"、"部门和员工" → 组合 看到 "层层包装"、"增强"、"加料" → 装饰

终极判断技巧 :看类图里组合对象持有的是 一个 引用还是 多个 引用。一个引用 → 装饰;多个引用(List/Array)→ 组合。


九、真题风格模拟与代码填空

模拟题 1(上午选择题)

以下关于组合模式的叙述中,正确的是()。

A. 组合模式属于创建型模式,主要用于创建树形结构的对象

B. 在组合模式中,Leaf 和 Composite 必须实现不同的接口,以便客户端区分处理

C. 组合模式使得客户端可以统一地对待单个对象和组合对象,无需区分部分和整体

D. 组合模式与装饰模式的核心区别在于:组合模式用于增强对象功能,装饰模式用于构建树形结构

答案:C

解析

  • A 错误:组合属于 结构型 模式,不是创建型。

  • B 错误:Leaf 和 Composite 实现 同一个 Component 接口,这正是组合模式的核心 ------ 统一接口。

  • C 正确:这是组合模式的核心定义。

  • D 错误:说反了。组合模式用于构建 树形结构 (统一处理部分和整体),装饰模式用于 增强功能(层层包装)。


模拟题 2(下午代码填空 --- 补全 Composite 的遍历逻辑)

某系统使用组合模式实现部门架构的薪资计算。OrgNode 是抽象组件,Employee 是叶子节点,Department 是组合节点。请补全(1)~(3)。

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

// Component
interface OrgNode {
    double calculateSalary();
    void add(OrgNode node);
}

// Leaf
class Employee implements OrgNode {
    private double salary;
    public Employee(double salary) { this.salary = salary; }
    public double calculateSalary() { return salary; }
    public void add(OrgNode node) {
        throw new UnsupportedOperationException();
    }
}

// Composite
class Department implements OrgNode {
    private List<OrgNode> (1)______ = new ArrayList<>();
    
    public void add(OrgNode node) {
        (2)______;
    }
    
    public double calculateSalary() {
        double total = 0;
        for (OrgNode member : (3)______) {
            total += member.calculateSalary();
        }
        return total;
    }
}

public class Client {
    public static void main(String[] args) {
        OrgNode dept = new Department();
        dept.add(new Employee(5000));
        dept.add(new Employee(6000));
        System.out.println("部门薪资总额: " + dept.calculateSalary());
    }
}

答案

  • (1) members(或 children / subordinates

  • (2) members.add(node)(或 children.add(node)

  • (3) members(或 children

阅卷要点

  • (1) 必须是 List<OrgNode> 类型的成员变量名,如 memberschildrensubordinates。这是组合节点存储子组件的核心数据结构。

  • (2) 必须是 members.add(node),将子组件加入列表。如果写 this.add(node)死循环(零分)。

  • (3) 必须是遍历子组件列表的变量名,与 (1) 一致。for-each 遍历是组合模式 operation() 的核心逻辑,缺少遍历会判错。


模拟题 3(下午代码填空 --- 补全 Component 接口与 Leaf 空实现)

某系统使用组合模式实现图形编辑器中的图形组合。Graphic 是抽象组件,LineRectangle 是叶子,Picture 是组合节点。Picture 可以包含多个 Graphic。请补全(1)~(4)。

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

// Component
interface Graphic {
    void draw();
    void (1)______(Graphic g);
    void remove(Graphic g);
}

// Leaf:直线
class Line implements Graphic {
    public void draw() {
        System.out.println("绘制直线");
    }
    public void add(Graphic g) {
        (2)______;
    }
    public void remove(Graphic g) {
        throw new UnsupportedOperationException("直线不能移除子图形");
    }
}

// Composite:图形组合
class Picture implements Graphic {
    private List<Graphic> graphics = new ArrayList<>();
    
    public void add(Graphic g) {
        graphics.add(g);
    }
    
    public void remove(Graphic g) {
        graphics.remove(g);
    }
    
    public void draw() {
        System.out.println("组合图形 {");
        for (Graphic g : graphics) {
            (3)______;
        }
        System.out.println("}");
    }
}

public class Client {
    public static void main(String[] args) {
        Graphic pic = new Picture();
        pic.add(new Line());
        pic.add(new Line());
        (4)______;
    }
}

答案

  • (1) add

  • (2) throw new UnsupportedOperationException("直线不能添加子图形")(或类似空实现/异常)

  • (3) g.draw()

  • (4) pic.draw()

阅卷要点

  • (1) 必须是 add,这是 Component 接口声明的统一添加方法。

  • (2) 叶子节点的 add() 必须 抛异常空实现 。如果写 graphics.add(g)零分 (叶子没有 graphics 列表)。

  • (3) 必须是 g.draw(),递归调用子图形的绘制方法。这是组合模式的核心遍历逻辑。

  • (4) 必须是 pic.draw(),客户端统一调用绘制方法。如果写 pic.add(...) 会扣分,因为题目要求的是 "统一绘制"。


十、常见陷阱与注意事项

陷阱 1:误认为组合是创建型模式

组合属于 结构型 模式,因为它解决的是 对象之间的结构关系 (树形组合),而不是对象的创建问题。软考上午题如果问"以下属于创建型模式的是",选项里出现"组合" 不能选

陷阱 2:Leaf 和 Composite 实现不同接口

这是软考 最高频的失分点 。组合模式的核心是 统一接口

  • ❌ 错误:Leaf implements LeafInterfaceComposite implements CompositeInterface(客户端需要区分)

  • ✅ 正确:Leaf implements ComponentComposite implements Component(客户端统一对待)

软考代码填空里,Leaf 和 Composite 必须都 implements 同一个接口(或 extends 同一个抽象类)。

陷阱 3:Composite 的 operation() 没有遍历子组件

这是软考 最常考的填空陷阱 。Composite 的 operation() 方法必须包含 遍历子组件并递归调用 的逻辑:

java 复制代码
for (Component child : children) {
    child.operation();
}

如果 Composite 的 operation() 只处理了自己而没有遍历子组件,那就失去了 "组合" 的意义,客户端需要自己遍历,模式就失效了。

陷阱 4:与装饰模式混淆

这是软考 最难的辨析题

  • 组合 :一个 Composite 持有 多个 Component(List<Component>),形成 树形 结构。

  • 装饰 :一个 Decorator 持有 一个 Component(private Component component),形成 链式 结构。

快速判断技巧:看类图里组合对象持有的是一个引用还是多个引用。一个引用 → 装饰;多个引用(List)→ 组合。

陷阱 5:Leaf 的 add/remove 方法处理不当

Leaf 的 add()remove() 方法通常有两种处理方式:

  1. 抛异常throw new UnsupportedOperationException()(最规范,明确告知不支持)

  2. 空实现:什么都不做(也可以,但不够严谨)

软考代码填空里,两种方式都算对,但抛异常更符合 "叶子节点没有子节点" 的语义。如果 Leaf 的 add() 里写了 children.add(c)零分(Leaf 没有 children 列表)。

陷阱 6:与外观模式混淆
组合模式 外观模式
处理 树形层次 结构(部分-整体) 简化 多个平级 子系统的调用
强调 统一接口(Leaf 和 Composite 同接口) 强调 简化入口(Facade 提供高层接口)
递归 遍历逻辑 无递归,顺序调用
陷阱 7:Component 接口设计过宽

Component 接口中声明了 add()remove()getChild() 等方法,但 Leaf 并不需要这些方法。这是组合模式的一个争议点("透明组合模式" vs "安全组合模式")。软考中默认考察的是 透明组合模式(Component 声明所有方法,Leaf 空实现或抛异常),因为这是 GoF 原书的标准做法。


十一、总结

要点 内容
定义 将对象组合成树形结构以表示 "部分-整体" 的层次结构,使得用户对单个对象和组合对象的使用具有一致性
分类 结构型模式(GoF 23 正式成员)
核心三角色 Component(抽象组件)、Leaf(叶子)、Composite(组合)
核心思想 统一接口:Leaf 和 Composite 实现同一接口,客户端无需区分部分和整体
Composite 的关键 维护 List<Component> childrenoperation() 中遍历子组件递归调用
Leaf 的 add/remove 通常抛 UnsupportedOperationException 或空实现
与装饰区别 组合:树形结构,一对多,统一处理部分和整体;装饰:链式结构,一对一,增强单个对象
与外观区别 组合:处理树形层次结构;外观:简化多个平级子系统的调用
适用场景 树形菜单、文件系统、组织架构、图形编辑器的组合图形
软考重点 代码填空(补全 Composite 的遍历递归逻辑);类图识别(树形结构 + 统一接口);与装饰的辨析
答题技巧 看到 "树形结构"、"部分整体"、"文件和文件夹统一处理"、"部门和员工统一计算" → 组合;看到 List<Component> + 递归遍历 → 确认组合

结构型模式完整总结 :至此,结构型模式七兄弟(适配器、桥接、组合、装饰、外观、享元、代理)已全部补完。它们共同解决的是 类与类之间的组合关系 问题:适配器转接口、桥接分维度、组合建树形、装饰套娃增强、外观简化入口、享元共享对象、代理控制访问。

系列预告 :下一篇将讲 装饰模式 ------ 当需要在不改变对象接口的前提下,动态地给对象添加额外职责时,如何用 "套娃" 的方式层层包装,实现功能的灵活扩展。咱们下回见。

相关推荐
ttod_qzstudio3 小时前
【软考设计模式】装饰模式:“套娃“式增强与动态职责扩展精讲
设计模式·装饰模式
烬羽4 小时前
MCP 多服务器架构实战:一个 Agent 同时操控地图、浏览器和文件
设计模式·设计
阿pin7 小时前
Android随笔-常用的设计模式
android·设计模式
儒雅的名7 小时前
GoF设计模式——策略模式
设计模式·bash·策略模式
折哥的程序人生 · 物流技术专研16 小时前
第4篇:Lambda 简化策略模式(Java 8+)
java·设计模式·策略模式·函数式编程·lambda·代码简化·扩充系列
jsonbro1 天前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
workflower1 天前
室内外配送机器人-应用路径
人工智能·机器学习·设计模式·矩阵·自动化
ttod_qzstudio1 天前
【软考设计模式】原型模式:“克隆“对象与深拷贝、浅拷贝精讲
设计模式·原型模式
geovindu1 天前
java: Builder Pattern
java·开发语言·后端·设计模式·建造者模式·生成器模式