大白话说Java设计模式-20-组合模式(业务实战篇)

📌 PDF:AI人工智能 — AI Agent智能体实战项目

大白话说Java设计模式-20-组合模式(业务实战篇):大白商城商品类目树的"枝叶结构"

📌 一句话本质:组合模式就是"树形结构",让单个对象和组合对象用同一套 API。

🏷️ 标签:组合模式 / Java 设计模式 / 树形结构 / 大白商城 / 类目树   🎯 适合:初中级后端 / 经常处理树形结构的工程师


目录


一、业务场景引入:商品类目树怎么设计?

大白商城 2024 年做了一次大改造,运营经理提了一个看似简单、实则"动全身"的需求:

“商品类目要支持无限层级。比如:电子 → 手机 → 苹果 → iPhone 15。我要能查任意节点的子节点、父节点、整棵树。”

我打开老代码一看,根本没法看

/**
 * 大白商城老的类目代码
 */
public class CategoryService {

    public void printAllCategories() {
        // ❌ 硬编码 3 层
        List<Category> level1 = categoryDao.findByLevel(1);
        for (Category c1 : level1) {
            System.out.println(c1.getName());
            List<Category> level2 = categoryDao.findByParentId(c1.getId());
            for (Category c2 : level2) {
                System.out.println("  " + c2.getName());
                List<Category> level3 = categoryDao.findByParentId(c2.getId());
                for (Category c3 : level3) {
                    System.out.println("    " + c3.getName());
                    // ❌ 还要再加 level4?level5?
                }
            }
        }
    }
}

这种代码的痛点

序号问题后果
硬编码 3 层超过 3 层崩
嵌套 for 循环加 1 层要改所有 for
每个层级单独查性能差(N+1 查询)
业务代码臃肿看不到核心逻辑
树形操作缺失没法查子树、路径

老板要的"无限层级 + 树形操作",怎么搞?

答案就是——组合模式

1.1 大白话讲透组合

继续打比方:

场景:大白商城商品类目是树形结构:

  • 电子(节点)
    • 手机(节点)
      • 苹果(节点)
        • iPhone 15(叶子)
        • iPhone 14(叶子)
      • 小米(节点)
    • 电脑(节点)

每个节点都"看起来一样"

  • 节点可以有子节点
  • 叶子没有子节点
  • 对客户端来说,"节点"和"叶子"是同一种东西——都能 add / remove / getName

这就是组合模式的核心让客户端一致地处理"单个对象"和"组合对象"

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

1.2 组合模式的 3 个真实场景

大白商城里,组合模式用在:

场景“树”
商品类目电子 → 手机 → 苹果 → iPhone 15
权限树菜单 → 子菜单 → 按钮
组织架构公司 → 部门 → 团队 → 员工
文件系统根目录 → 子目录 → 文件
评论回复评论 → 回复 → 回复的回复

任何"树形结构"的场景,都用组合模式


二、反面教材:树形结构的"灾难现场"

我们看 4 个反面教材,看它们是怎么一步步崩的。

2.1 反面教材 v1:硬编码层级

/**
 * ❌ 反面教材 v1:硬编码层级
 */
public void printTree(List<Category> level1) {
    for (Category c1 : level1) {
        System.out.println(c1.getName());
        List<Category> level2 = categoryDao.findByParentId(c1.getId());
        for (Category c2 : level2) {
            System.out.println("  " + c2.getName());
            List<Category> level3 = categoryDao.findByParentId(c2.getId());
            for (Category c3 : level3) {
                // ... 又要加 level4
            }
        }
    }
}

翻车现场

序号问题后果
层数写死加 1 层要改方法
N+1 查询性能差
代码臃肿嵌套 4 层循环

2.2 反面教材 v2:每个层级一个类

/**
 * ❌ 反面教材 v2:每个层级一个类
 */
public class Level1Category { ... }
public class Level2Category extends Level1Category { ... }
public class Level3Category extends Level2Category { ... }
// ... 无限子类

翻车现场

序号问题后果
类数量爆炸层数越多类越多
代码重复每个层级类相似

2.3 反面教材 v3:Map 嵌套

/**
 * ❌ 反面教材 v3:Map 嵌套
 */
public void printTree(Map<String, Object> tree) {
    System.out.println(tree.get("name"));
    List<Map<String, Object>> children = (List<Map<String, Object>>) tree.get("children");
    if (children != null) {
        for (Map<String, Object> child : children) {
            printTree(child);  // 递归
        }
    }
}

翻车现场

序号问题后果
类型不安全编译期不检查
IDE 难重构改字段名不报错
业务逻辑散落不集中

2.4 反面教材 v4:直接用数据库递归查询

/**
 * ❌ 反面教材 v4:每次递归查数据库
 */
public void printTree(Long parentId, int level) {
    List<Category> children = categoryDao.findByParentId(parentId);  // 每次查
    for (Category child : children) {
        System.out.println("  ".repeat(level) + child.getName());
        printTree(child.getId(), level + 1);  // 递归
    }
}

翻车现场

序号问题后果
N+1 查询100 个节点 = 100 次查库
性能差树大时崩溃

2.5 4 个反面教材的共同病根

痛点反模式方案能不能解决?
无限层级❌ 全部写死
单次查整棵树❌ 全部多次查
客户端一致处理❌ 全部区分
树形操作❌ 全部缺失

必须上组合模式


三、模式原理:组合的"枝叶同形 + 一张图"

3.1 组合的 3 个核心角色

角色职责例子
抽象组件(Component)定义叶子和容器的共同接口CategoryComponent
叶子(Leaf)没有子节点GoodsCategory(具体商品类目)
容器(Composite)有子节点,也实现 ComponentCategoryNode(中间类目)

关键点容器和叶子实现同一接口,客户端无差别使用。

3.2 一张图看懂组合

CategoryComponent(接口)
  ├── add(Component c)
  ├── remove(Component c)
  ├── getName()
  └── getChildren()

        ↑                    ↑
        │ implements         │ implements
        │                    │
CategoryNode(容器)   GoodsCategory(叶子)
  ├── List<Component> children
  ├── add(c) { children.add(c) }
  ├── getName() { return "电子" }
  └── getChildren() { return children }

                  ↑   ↑   ↑
                  │   │   │
              CategoryNode CategoryNode GoodsCategory
                (手机)    (苹果)    (iPhone 15)

3.3 组合的"灵魂三问"

Q1:组合 vs 继承,区别是什么?

答:

  • 组合has-a 关系(节点持有子节点)
  • 继承is-a 关系(叶子继承父类)
  • 组合不依赖具体类,依赖抽象
  • 组合更灵活

Q2:组合 vs 装饰器,区别是什么?

答:

  • 组合整体-部分(树形结构)
  • 装饰器功能叠加(链式包装)
  • 组合侧重"结构",装饰器侧重"行为"
  • 组合是 is-part-of装饰器是 has-a

Q3:客户端怎么统一处理节点和叶子?

答:

  • 节点和叶子都实现 Component 接口
  • 客户端调 component.operation()不关心是节点还是叶子
  • 节点递归调子节点的 operation()
  • 叶子只执行自己的 operation()

3.4 组合的 3 种实现方式

方式特点适用
透明式叶子也实现 add/remove(抛异常)客户端完全统一
安全式叶子不实现 add/remove客户端要判断类型
混合式默认实现,子类可覆盖灵活

大白商城主推透明式(统一接口)。


四、实战代码:大白商城商品类目树完整实现

下面是大白商城生产环境在用的组合实现,全套代码可直接复制到 IDEA 跑

4.1 项目环境与依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
                             https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.dabai.mall</groupId>
    <artifactId>mall-design-pattern-20</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    <name>mall-design-pattern-20</name>
    <description>大白商城 - 设计模式 20 组合模式</description>

    <properties>
        <java.version>17</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

4.2 抽象组件:CategoryComponent

package com.dabai.mall.category;

import java.util.List;

/**
 * ✅ 抽象组件:商品类目组件
 * <p>
 * 节点和叶子都实现这个接口
 *
 * @author 大白商城技术团队
 */
public interface CategoryComponent {

    /**
     * 获取类目 ID
     */
    Long getId();

    /**
     * 获取类目名称
     */
    String getName();

    /**
     * ✅ 树形操作
     */
    void add(CategoryComponent component);

    /**
     * ✅ 树形操作
     */
    void remove(CategoryComponent component);

    /**
     * ✅ 树形操作
     */
    List<CategoryComponent> getChildren();

    /**
     * ✅ 树形操作:打印
     */
    void print(int level);
}

4.3 容器:CategoryNode

package com.dabai.mall.category;

import lombok.Getter;

import java.util.ArrayList;
import java.util.List;

/**
 * ✅ 容器:类目节点(有子节点)
 * <p>
 * 关键:实现 CategoryComponent 接口
 */
@Getter
public class CategoryNode implements CategoryComponent {

    private final Long id;
    private final String name;
    private final List<CategoryComponent> children = new ArrayList<>();

    public CategoryNode(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public void add(CategoryComponent component) {
        children.add(component);
    }

    @Override
    public void remove(CategoryComponent component) {
        children.remove(component);
    }

    @Override
    public List<CategoryComponent> getChildren() {
        return children;
    }

    @Override
    public void print(int level) {
        // 打印当前节点
        System.out.println("  ".repeat(level) + "📁 " + getName());
        // ✅ 递归打印子节点
        for (CategoryComponent child : children) {
            child.print(level + 1);
        }
    }
}

4.4 叶子:GoodsCategory

package com.dabai.mall.category;

import lombok.Getter;

import java.util.Collections;
import java.util.List;

/**
 * ✅ 叶子:商品类目(无子节点)
 * <p>
 * 关键:实现 CategoryComponent 接口
 */
@Getter
public class GoodsCategory implements CategoryComponent {

    private final Long id;
    private final String name;
    private final Long goodsCount;  // 商品数量

    public GoodsCategory(Long id, String name, Long goodsCount) {
        this.id = id;
        this.name = name;
        this.goodsCount = goodsCount;
    }

    @Override
    public void add(CategoryComponent component) {
        // 叶子不能 add,抛异常
        throw new UnsupportedOperationException("叶子节点不能 add");
    }

    @Override
    public void remove(CategoryComponent component) {
        throw new UnsupportedOperationException("叶子节点不能 remove");
    }

    @Override
    public List<CategoryComponent> getChildren() {
        return Collections.emptyList();  // 叶子无子节点
    }

    @Override
    public void print(int level) {
        // 打印叶子节点
        System.out.println("  ".repeat(level) + "📄 " + getName() + " (" + goodsCount + " 件)");
    }
}

4.5 构建类目树

package com.dabai.mall.category;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * ✅ 商品类目服务
 *
 * @author 大白商城技术团队
 */
@Slf4j
@Service
public class CategoryService {

    /**
     * 构建大白商城的完整类目树
     */
    public CategoryComponent buildCategoryTree() {
        // 根节点:全部商品
        CategoryNode root = new CategoryNode(0L, "全部商品");

        // 一级:电子
        CategoryNode electronics = new CategoryNode(1L, "电子");
        root.add(electronics);

        // 二级:手机
        CategoryNode phones = new CategoryNode(2L, "手机");
        electronics.add(phones);

        // 三级:苹果
        CategoryNode apple = new CategoryNode(3L, "苹果");
        phones.add(apple);

        // 四级:iPhone 15 / iPhone 14(叶子)
        apple.add(new GoodsCategory(4L, "iPhone 15 Pro", 100L));
        apple.add(new GoodsCategory(5L, "iPhone 14", 50L));

        // 三级:小米
        CategoryNode xiaomi = new CategoryNode(6L, "小米");
        phones.add(xiaomi);
        xiaomi.add(new GoodsCategory(7L, "小米 14", 80L));

        // 二级:电脑
        CategoryNode computers = new CategoryNode(8L, "电脑");
        electronics.add(computers);
        computers.add(new GoodsCategory(9L, "MacBook Pro", 30L));

        // 一级:服饰
        CategoryNode clothing = new CategoryNode(10L, "服饰");
        root.add(clothing);
        clothing.add(new GoodsCategory(11L, "T恤", 200L));

        return root;
    }

    /**
     * 打印整棵树
     */
    public void printTree() {
        CategoryComponent root = buildCategoryTree();
        root.print(0);
    }

    /**
     * 统计整棵树的商品数
     */
    public long countGoods(CategoryComponent component) {
        if (component instanceof GoodsCategory) {
            return ((GoodsCategory) component).getGoodsCount();
        }
        long total = 0;
        for (CategoryComponent child : component.getChildren()) {
            total += countGoods(child);
        }
        return total;
    }

    /**
     * 查找节点
     */
    public CategoryComponent findById(CategoryComponent component, Long id) {
        if (component.getId().equals(id)) {
            return component;
        }
        for (CategoryComponent child : component.getChildren()) {
            CategoryComponent found = findById(child, id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }
}

4.6 完整类目树结构

全部商品
├── 📁 电子
│   ├── 📁 手机
│   │   ├── 📁 苹果
│   │   │   ├── 📄 iPhone 15 Pro (100 件)
│   │   │   └── 📄 iPhone 14 (50 件)
│   │   └── 📁 小米
│   │       └── 📄 小米 14 (80 件)
│   └── 📁 电脑
│       └── 📄 MacBook Pro (30 件)
└── 📁 服饰
    └── 📄 T恤 (200 件)

4.7 单元测试

package com.dabai.mall.category;

import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

/**
 * 组合模式完整单元测试
 */
class CategoryTreeTest {

    @Test
    void testPrintTree() {
        CategoryService service = new CategoryService();
        // 打印树(观察输出)
        service.printTree();
    }

    @Test
    void testCountGoods() {
        CategoryService service = new CategoryService();
        CategoryComponent root = service.buildCategoryTree();
        // 100 + 50 + 80 + 30 + 200 = 460
        assertEquals(460L, service.countGoods(root));
    }

    @Test
    void testFindById() {
        CategoryService service = new CategoryService();
        CategoryComponent root = service.buildCategoryTree();

        // 查找"苹果"节点
        CategoryComponent apple = service.findById(root, 3L);
        assertNotNull(apple);
        assertEquals("苹果", apple.getName());

        // 查找"iPhone 15"叶子
        CategoryComponent iphone = service.findById(root, 4L);
        assertNotNull(iphone);
        assertEquals("iPhone 15 Pro", iphone.getName());

        // 查找不存在的 ID
        CategoryComponent notFound = service.findById(root, 999L);
        assertNull(notFound);
    }

    @Test
    void testLeafCannotAdd() {
        // 叶子不能 add
        GoodsCategory leaf = new GoodsCategory(4L, "iPhone 15 Pro", 100L);
        assertThrows(UnsupportedOperationException.class,
                () -> leaf.add(new GoodsCategory(5L, "test", 1L)));
    }

    @Test
    void testCompositeAndLeafUnified() {
        // ✅ 客户端不区分节点和叶子
        CategoryService service = new CategoryService();
        CategoryComponent root = service.buildCategoryTree();

        // 调用 root.print() 不管里面是节点还是叶子
        root.print(0);

        // 调用 root.getChildren() 拿到的是 List<CategoryComponent>
        List<CategoryComponent> topLevel = root.getChildren();
        assertEquals(2, topLevel.size());  // 电子 + 服饰
    }
}

五、组合 vs 继承 vs 装饰器

5.1 一张表看清区别

模式关注点关系适用场景
组合整体-部分has-a(树形)树形结构
继承父子关系is-a类层次
装饰器功能叠加has-a(链式)动态增强

5.2 一个具体例子区分 3 个模式

大白商城

模式例子
组合商品类目树:电子 → 手机 → 苹果
继承VipOrder extends Order:VIP 订单
装饰器价格计算:满减 + 折扣 + 运费叠加

5.3 决策树

数据是树形结构?
├── 是 → ✅ 组合模式
└── 否
    ├── 类之间有 is-a 关系?
    │   ├── 是 → ✅ 继承
    │   └── 否
    │       ├── 动态叠加功能?
    │       │   ├── 是 → ✅ 装饰器
    │       │   └── 否 → 普通类

5.4 组合 vs 装饰器:易混淆

维度组合装饰器
结构树形链式
数量N 个子节点1 个被装饰者
递归递归遍历子节点链式调用
类比文件夹包装纸

大白商城选型

场景选哪个原因
商品类目组合树形结构
价格计算装饰器链式叠加
权限树组合树形结构
日志装饰装饰器链式叠加

六、JDK 的 File 类是组合模式吗?

6.1 java.io.File:文件系统树

源码位置

  • OpenJDK 17
  • src/java.base/share/classes/java/io/File.java

核心源码(简化版):

/**
 * ✅ JDK 组合模式:File 类
 * <p>
 * File 既可以表示"文件"(叶子),也可以表示"目录"(容器)
 */
public class File implements Comparable<File>, Serializable {

    /**
     * ✅ 叶子操作
     */
    public boolean isFile() { ... }
    public long length() { ... }

    /**
     * ✅ 容器操作
     */
    public boolean isDirectory() { ... }
    public File[] listFiles() { ... }
    public boolean mkdir() { ... }

    /**
     * ✅ 删除
     */
    public boolean delete() { ... }
}

完整树形操作

/**
 * ✅ 递归遍历文件系统
 */
public void printFileTree(File file, int level) {
    System.out.println("  ".repeat(level) + (file.isDirectory() ? "📁 " : "📄 ") + file.getName());

    if (file.isDirectory()) {
        File[] children = file.listFiles();
        if (children != null) {
            for (File child : children) {
                printFileTree(child, level + 1);
            }
        }
    }
}

// 使用
printFileTree(new File("/data"), 0);

关键点

关键点解释
isFile() / isDirectory()区分叶子/容器
listFiles()容器的子节点
File 本身同时支持文件(叶子)和目录(容器)

6.2 树形结构的核心操作

4 大核心操作

操作解释大白商城例子
遍历访问所有节点打印整棵树
查找按条件找节点查"苹果"节点
统计汇总所有叶子统计商品数
过滤留下满足条件的查所有"电子"

完整实现

/**
 * 树形结构的 4 大操作
 */
public class CategoryOperations {

    /**
     * 1. 遍历:打印
     */
    public void traverse(CategoryComponent node, int level) {
        System.out.println("  ".repeat(level) + node.getName());
        for (CategoryComponent child : node.getChildren()) {
            traverse(child, level + 1);
        }
    }

    /**
     * 2. 查找:按 ID
     */
    public CategoryComponent findById(CategoryComponent node, Long id) {
        if (node.getId().equals(id)) return node;
        for (CategoryComponent child : node.getChildren()) {
            CategoryComponent found = findById(child, id);
            if (found != null) return found;
        }
        return null;
    }

    /**
     * 3. 统计:叶子数量
     */
    public int countLeaves(CategoryComponent node) {
        if (node instanceof GoodsCategory) return 1;
        int total = 0;
        for (CategoryComponent child : node.getChildren()) {
            total += countLeaves(child);
        }
        return total;
    }

    /**
     * 4. 过滤:按名字
     */
    public List<CategoryComponent> filterByName(CategoryComponent node, String keyword) {
        List<CategoryComponent> result = new ArrayList<>();
        if (node.getName().contains(keyword)) {
            result.add(node);
        }
        for (CategoryComponent child : node.getChildren()) {
            result.addAll(filterByName(child, keyword));
        }
        return result;
    }
}

七、工程决策 Checklist

7.1 ✅ 这 5 种情况,强烈建议用组合

序号场景原因
树形结构数据类目/权限/组织
无限层级动态层级
整体-部分关系文件系统
统一处理节点和叶子一致性
递归操作树形遍历

7.2 ❌ 这 5 种情况,绝对不要用组合

序号场景原因
数据是扁平的不需要树形
层级固定用枚举/常量
节点和叶子行为差异大拆开两个类
性能敏感递归开销大
树太深(>1000 层)栈溢出

7.3 ⚠️ 组合的 6 大常见坑

序号表现解决方案
循环引用节点 A 包含 B,B 包含 A检查 ID 唯一
栈溢出树太深递归改用迭代
叶子不能 add抛 UnsupportedOperationException默认实现
类型判断instanceof GoodsCategory用访问者模式
性能差每次递归遍历缓存结果
数据库 N+1每个节点一次查询一次查整树

7.4 面试官视角:组合高频追问

Q1:组合 vs 装饰器,区别是什么?

答:组合整体-部分(树形),装饰器功能叠加(链式)。组合是 is-part-of,装饰器是 has-a。

Q2:JDK 的 File 是组合模式吗?

答:是的。File 既可以表示"文件"(叶子),也可以表示"目录"(容器)。isFile() / isDirectory() / listFiles() 是关键 API。

Q3:组合模式的"透明式"vs"安全式"?

答:透明式叶子也实现 add/remove(抛异常),客户端完全统一。安全式叶子不实现 add/remove,客户端要判断类型。推荐透明式(统一接口)。


八、与其他模式协作

8.1 组合 + 访问者 = 在树上做各种操作

/**
 * 组合 + 访问者:树形数据 + 各种操作
 */
public interface CategoryVisitor {
    void visit(GoodsCategory category);
    void visit(CategoryNode node);
}

public class CountVisitor implements CategoryVisitor {
    private long totalGoods = 0;

    @Override
    public void visit(GoodsCategory category) {
        totalGoods += category.getGoodsCount();
    }

    @Override
    public void visit(CategoryNode node) {
        // 节点本身不统计
    }
}

8.2 组合 + 迭代器 = 树形遍历

/**
 * 组合 + 迭代器:树形遍历
 */
public class CategoryIterator implements Iterator<CategoryComponent> {

    private final Queue<CategoryComponent> queue = new LinkedList<>();

    public CategoryIterator(CategoryComponent root) {
        queue.add(root);
    }

    @Override
    public boolean hasNext() {
        return !queue.isEmpty();
    }

    @Override
    public CategoryComponent next() {
        CategoryComponent node = queue.poll();
        queue.addAll(node.getChildren());
        return node;
    }
}

8.3 组合 + 享元 = 共享叶子节点

/**
 * 组合 + 享元:相同叶子共享
 */
public class CategoryFlyweight {

    private static final Map<String, GoodsCategory> CACHE = new ConcurrentHashMap<>();

    public static GoodsCategory get(Long id, String name, Long count) {
        String key = id + ":" + name;
        return CACHE.computeIfAbsent(key, k -> new GoodsCategory(id, name, count));
    }
}

8.4 大白商城模式协作全景图

                    ┌──────────────┐
                    │   组合      │ ← 本篇
                    └──────┬───────┘
                           │
       ┌───────────┬───────┼───────┬───────────┐
       │           │       │       │           │
   ┌───▼───┐  ┌────▼───┐ ┌▼────┐ ┌▼─────┐  ┌───▼────┐
   │访问者│  │迭代器  │ │享元 │ │责任链│  │ 装饰器 │
   │(操作) │  │(遍历) │ │(共享)│ │(链)  │  │(叠加) │
   └───────┘  └────────┘ └──────┘ └──────┘  └───────┘
   43 篇       39 篇     23 篇     31 篇     16 篇

九、本篇小结 + 下篇预告

9.1 本篇小结(5 个核心要点)

  1. 本质:组合 = 树形结构,让客户端一致处理节点和叶子
  2. 场景:类目/权限/组织/文件系统,任何树形都用组合
  3. 对比:组合 vs 继承(is-part-of vs is-a)/ 组合 vs 装饰器(树形 vs 链式)。
  4. JDK File:经典组合模式,isFile / isDirectory / listFiles 三大 API。
  5. 避坑:循环引用 / 栈溢出 / 叶子抛异常 / 数据库 N+1。

9.2 一句话总结

组合不是"换种方式存数据",是"用树形结构 + 统一接口处理"。大白商城的商品类目从硬编码 3 层改成无限层级,订单组织架构从扁平表改成树形——业务和性能都大幅提升

9.3 知识脑图

组合模式
├── 3 大角色
│   ├── 抽象组件(CategoryComponent)
│   ├── 容器(CategoryNode)
│   └── 叶子(GoodsCategory)
├── 4 大操作
│   ├── 遍历(print)
│   ├── 查找(findById)
│   ├── 统计(count)
│   └── 过滤(filter)
├── 3 种实现
│   ├── 透明式(叶子实现 add/remove 抛异常)✅
│   ├── 安全式(叶子不实现 add/remove)
│   └── 混合式
├── 模式对比
│   ├── vs 继承(is-part-of vs is-a)
│   ├── vs 装饰器(树形 vs 链式)
│   └── vs 享元(独立 vs 共享)
├── JDK File
│   ├── isFile()(叶子判断)
│   ├── isDirectory()(容器判断)
│   └── listFiles()(子节点)
└── 模式协作
    ├── + 访问者(在树上操作)
    ├── + 迭代器(树形遍历)
    └── + 享元(共享叶子)

9.4 下篇预告

第 21 篇【组合模式 - 源码剖析篇】:JDK / AWT / MyBatis 中的组合实现

下一篇我们会深入源码,回答三个问题:

  1. JDK 的 java.io.File 怎么用组合表示文件系统?
  2. AWT 的 Container / Component 怎么用组合表示 UI 树?
  3. MyBatis 的 SqlNode 怎么用组合表示动态 SQL 树?

并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


觉得对您有帮助,麻烦点点关注啦,您的关注是我创作的最大动力~ 🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机小能手(AI+Java)

若对您有所帮助,请点点关注哟~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值