【5】设计模式公理化推演体系_完备性宣言
设计模式公理化推演体系·完备性宣言
5 公理 · 7 范式 · 2 判据 · 31 模式 · 明确边界
本书定位:本书是《设计模式公理化推演体系·增补修正版》的续篇,也是整个"设计模式公理化"系列的完备性宣言。
在前两本书中,我们完成了从 7-6-23 到 5-7-23 的体系精炼,并识别了工程有效性判据。本书在此基础上,完成三件事:
- 演绎:将 GoF 之后 8 个极其流行的模式纳入基础/交叉范式,使体系从 23 扩展到 31;
- 概括:给出 5-7-2 框架的完整总览,证明其内部自洽;
- 边界:明确声明体系不覆盖什么,以及为什么不覆盖——公理化体系的尊严来自对其边界的诚实。
如果你已经阅读过前两本书,本书将帮助你建立最终的"模式地图";如果你直接阅读本书,建议先通读附录 A 的"5 分钟速览",再进入正文。
目录
- 前言:为什么需要一份完备性宣言
- 第一章:体系总览——5-7-2 框架的最终形态
- 第二章:新模式的纳入推导——从 23 到 31
- 第三章:边界声明——体系不覆盖什么
- 第四章:完备性证明与自洽性
- 附录 A:5 分钟速览
- 附录 B:31 模式完整归属总表
- 附录 C:新旧体系三版对照
前言:为什么需要一份完备性宣言
0.1 从经验到公理的三级跳
设计模式的学习通常经历三个阶段:
阶段一:背诵阶段
“单例模式保证全局唯一实例,工厂方法将实例化延迟到子类…”
问题:23 个模式是孤立的工具,不知道何时用、为什么用。
阶段二:归纳阶段
“创建型模式解决对象创建问题,结构型模式解决对象组合问题…”
问题:GoF 的三分法是按"代码角色"分类,不是按"思维模型"分类。观察者模式和中介者模式都在"行为型",但一个是一对多广播,一个是多对多中转——思维完全不同。
阶段三:演绎阶段
“从 5 条不可再约的存在公理出发,推导出 7 大思维范式,再实例化为 31 个模式…”
问题:公理化体系是否完备?是否遗漏了重要的流行模式?是否把不属于体系的东西硬塞了进来?
本书回答这三个问题。
0.2 完备性的两个维度
完备性不是"覆盖一切"。数学上,完备性有两种含义:
- 相对完备(Relative Completeness):在给定边界内,所有合法命题都可被证明或证伪。
- 绝对完备(Absolute Completeness):不存在边界,覆盖全部可能命题——哥德尔第一不完备定理已经证明,任何足够强的形式系统都不可能绝对完备。
5-7-2 框架追求的是相对完备:
在"面向对象系统结构设计"这个边界内,从 5 条核心存在公理出发,通过严格的逻辑推导,可以覆盖所有主流的对象结构设计模式。
本书将证明这个命题,并明确边界在哪里。
第一章:体系总览——5-7-2 框架的最终形态
1.1 5 条核心存在公理
公理只描述"系统必然面对什么",不描述"你应该怎么做"。
| 编号 | 命名 | 核心描述 | 学科源头 |
|---|---|---|---|
| A1 | 实体化公理 | 软件实体必然经历生成→存在→消亡的连续过程 | 本体论、系统论 |
| A2 | 分层公理 | 复杂交互必然呈现层次结构,层次是复杂性的必然产物 | 系统论、认知科学 |
| A3 | 组合公理 | 结构必然由更小单元组合而成,组合操作对结构集合封闭 | 离散数学、本体论 |
| A4 | 行为可变公理 | 同一结构在不同上下文下必然表现出不同行为 | 形式逻辑、运筹学 |
| A5 | 信息拓扑公理 | 信息传递必然选择某种拓扑结构,拓扑决定耦合度 | 信息论、图论 |
1.2 2 个工程有效性判据
判据不参与推导,只判决推导结果是否工程可用。
| 编号 | 命名 | 判决内容 | 拦截示例 |
|---|---|---|---|
| V1 | 内聚性判据 | 推导结果的职责集中度必须高于阈值 | 拦截"上帝创建器"——一个类负责所有对象的创建 |
| V2 | 演化性判据 | 推导结果必须支持通过新增实体来扩展 | 拦截"修改源码才能新增类型"的简单工厂 |
1.3 7 大思维范式
| 编号 | 命名 | 推导来源 | 推导类型 | 包含模式数 |
|---|---|---|---|---|
| P1 | 创建控制范式 | A1 | 基础推导 | 8 |
| P2 | 接口统一范式 | A2 | 基础推导 | 6 |
| P3 | 递归组合范式 | A3 | 基础推导 | 3 |
| P4 | 行为可变范式 | A4 | 基础推导 | 4 |
| P5 | 信息流转范式 | A5 | 基础推导 | 5 |
| P6 | 结构操作范式 | A3 × A4 | 交叉推导 | 4 |
| P7 | 领域叠加范式 | A1~A5 + 领域公理 | 叠加推导 | 1+ |
总计:31 个核心模式(23 GoF + 8 新增)
1.4 三层推导关系图
┌─────────────────────────────────────────┐
│ 第一层:5 条核心存在公理(描述性) │
│ A1 实体化 A2 分层 A3 组合 A4 行为可变 A5 信息拓扑 │
└─────────────────────────────────────────┘
↓ 基础推导 ↓ 交叉推导
┌─────────────────────────────────────────┐
│ 第二层:7 大思维范式(结构性) │
│ P1 创建控制 P2 接口统一 P3 递归组合 │
│ P4 行为可变 P5 信息流转 P6 结构操作 │
│ P7 领域叠加 │
└─────────────────────────────────────────┘
↓ 实例化
┌─────────────────────────────────────────┐
│ 第三层:31 个核心模式(实例性) │
│ 23 GoF 模式 + 8 个新增流行模式 │
└─────────────────────────────────────────┘
┌─────────────────────────────────────────┐
│ 判决层:2 个工程有效性判据 │
│ V1 内聚性判据 V2 演化性判据 │
│ (不参与推导,只判决推导结果是否可用) │
└─────────────────────────────────────────┘
第二章:新模式的纳入推导——从 23 到 31
2.1 纳入原则
新增模式必须满足三个条件:
- 流行度:工业级代码中天天在用,不是学术玩具;
- 推导性:可以从 A1–A5 直接推导或交叉推导,无需诉诸领域公理;
- 非冗余:不是已有模式的简单语法变体,而是独立的约束响应。
2.2 归入 P1 创建控制的 3 个新模式
2.2.1 对象池模式(Object Pool)
流行度:⭐⭐⭐ 极高。数据库连接池、线程池、游戏对象池、HTTP 连接池——几乎所有高性能系统都在用。
是什么
对象池模式的核心思想是:预先创建一组对象,放入"池"中统一管理;当客户端需要对象时,从池中"租借"一个已存在的对象;使用完毕后"归还"到池中,而非销毁。对象的创建成本(时间或资源)远高于重置成本时,对象池成为必然选择。
对象池与单例、原型的本质区别在于:
- 单例:全局只有 1 个实例,所有人共享同一个对象
- 原型:通过复制已有对象来创建新对象
- 对象池:预先创建 N 个实例(N ≥ 1),循环复用,控制总数量上限
几时出现的
对象池的概念最早出现在 1990 年代中期的数据库连接池(如 JDBC Connection Pool)和线程池(如 Java ThreadPoolExecutor)中。2000 年代随着 Java EE 应用服务器的普及,连接池成为标准配置。2010 年代后,对象池思想扩展到游戏开发(Unity 对象池、对象池管理器)、网络编程(Netty 的 ByteBuf 池、HTTP 连接池)、以及高性能计算(内存池、缓冲区池)。
哪里用的多
| 领域 | 典型实现 | 为什么必须池化 |
|---|---|---|
| 数据库访问 | HikariCP、Druid、C3P0 | 数据库连接创建需要 TCP 握手 + 认证,耗时数百毫秒 |
| 线程管理 | Java ThreadPoolExecutor、.NET ThreadPool | 线程创建涉及内核态切换,成本极高 |
| 游戏开发 | Unity Object Pool、Unreal Object Pool | 每帧创建/销毁游戏对象导致 GC 压力,帧率暴跌 |
| 网络通信 | Netty ByteBuf Pool、OkHttp Connection Pool | 内存分配和 TCP 连接创建都是高频高成本操作 |
| 图形渲染 | GPU 纹理池、顶点缓冲区池 | GPU 资源创建需要驱动交互,延迟极高 |
与 P1 内现有模式的关系
- 单例管控"实例数量为 1"
- 原型管控"通过复制创建"
- 对象池管控"预创建 + 循环复用 + 上限控制"
三者是 A1(实体化公理)在不同成本约束下的三个分支:
- 当"只需要 1 个" → 单例
- 当"复制比新建快" → 原型
- 当"预创建 + 复用比即时创建快" → 对象池
为什么不是 P7(领域叠加)?
对象池不是"并发领域专属"。它的核心逻辑与线程无关——任何创建成本高昂且可复用的实体都应该池化:字符串构建器、图形资源、网络句柄。即使在单线程程序中,数据库连接池仍然是必需品。
推导链:
A1: 实体必然经历创建→存在→消亡
↓
约束: 创建时间成本 >> 复用时间成本,且实体状态可重置
↓
P1-规则: 创建过程必须预分配 + 租借归还,而非即时创建即时销毁
↓
模式: 对象池(Object Pool)
工业级案例
- HikariCP:目前 Java 世界最快的数据库连接池,核心思想就是"池化 + 轻量级管理"
- Netty PooledByteBufAllocator:Netty 的高性能秘诀之一,通过池化 ByteBuf 避免 JVM GC 压力
- Unity Object Pool:Unity 2021 官方引入的 ObjectPool API,游戏对象池化成为标准实践
- Go sync.Pool:Go 语言标准库中的对象池,用于减少 GC 压力,在高并发服务中广泛使用
2.2.2 DI / IoC(依赖注入 / 控制反转)
流行度:⭐⭐⭐ 极高。Spring、ASP.NET Core、Angular、Unity、Laravel——现代框架的基石。没有 DI,就没有现代企业级开发。
是什么
依赖注入(Dependency Injection, DI)和控制反转(Inversion of Control, IoC)是同一枚硬币的两面:
- IoC 是原则:对象的依赖关系不由对象自己创建和管理,而是由外部容器控制
- DI 是实现:容器通过构造函数注入、Setter 注入、接口注入等方式,将依赖对象"注入"到需要它的对象中
核心思想极其简单:不要自己 new 依赖,让外面传进来。
// 没有 DI:对象自己创建依赖,耦合死了
class OrderService {
private UserRepository repo = new UserRepository(); // 硬编码!
}
// 有 DI:依赖由外部传入,对象只声明"我需要什么"
class OrderService {
private final UserRepository repo;
OrderService(UserRepository repo) { // 构造函数注入
this.repo = repo;
}
}
几时出现的
- 2002 年:Martin Fowler 发表《Inversion of Control Containers and the Dependency Injection pattern》,正式命名 DI 模式
- 2004 年:Spring Framework 1.0 发布,IoC 容器成为 Java 企业开发的标配
- 2008 年:.NET 引入 Unity Container、Castle Windsor,DI 思想进入 .NET 生态
- 2010 年代:Angular(2010)、Laravel(2011)、ASP.NET Core(2016)将 DI 作为框架核心机制,而非可选插件
- 2020 年代:DI 已成为所有现代框架的"空气"——你几乎意识不到它的存在,但没有它系统无法呼吸
哪里用的多
| 框架/平台 | DI 实现 | 注入方式 |
|---|---|---|
| Java/Spring | Spring IoC Container | 注解(@Autowired/@Inject)+ 构造函数 |
| .NET Core | Microsoft.Extensions.DependencyInjection | 构造函数注入(原生支持) |
| JavaScript/Angular | Angular Injector | 构造函数 + @Injectable 装饰器 |
| PHP/Laravel | Laravel Service Container | 构造函数 + 服务提供者 |
| Unity 游戏引擎 | Unity Dependency Injection | 构造函数 + 属性注入 |
| Go | wire、fx | 代码生成 + 构造函数 |
为什么从 P7 回归 P1?
原书将 DI/IoC 放在范式 6(架构综合),理由是"需要同时满足分层、解耦、可扩展等多重约束"。但这过度复杂化了。
DI/IoC 的本质只有一个:对象不自己创建它的依赖,创建职责外置给容器。这是纯粹的生命周期管理问题——A1(实体化公理)的核心关切。
- 对象 A 依赖对象 B → A1 要求 B 必须被创建
- A 自己 new B → 创建职责分散在业务代码中,生命周期混乱
- 容器统一管理 B 的创建、缓存、销毁 → 创建职责集中,生命周期受控
与工厂方法的区别:
- 工厂方法:客户端主动选择具体工厂(
new ConcreteFactory().createProduct()) - DI/IoC:客户端不选择,容器根据配置自动装配(
container.get(OrderService.class)自动注入所有依赖)
两者都是"创建控制",只是控制权的粒度不同:工厂是"类级",DI 是"系统级"。
推导链:
A1: 实体必然经历创建→存在→消亡
↓
约束: 对象之间存在依赖关系,手动创建导致耦合且生命周期混乱
↓
P1-规则: 创建逻辑必须与使用逻辑分离,且依赖的创建由外部容器统一管理
↓
模式: 依赖注入 / 控制反转(DI / IoC)
工业级案例
- Spring Framework:全世界最经典的 IoC 容器实现,管理所有 Bean 的创建、依赖注入、生命周期回调、AOP 代理包装
- ASP.NET Core:.NET 的 DI 容器是框架内核的一部分,从 WebHost 启动到 Controller 实例化,全程依赖注入
- Angular:组件的依赖通过构造函数注入,服务通过
@Injectable标记为可注入,模块通过providers数组注册 - Laravel:服务容器(Service Container)是 Laravel 的"心脏",路由、控制器、中间件的所有依赖都由容器自动解析
2.2.3 流式建造者(Fluent Builder)
流行度:⭐⭐ 高。Java 的 StringBuilder、Lombok 的 @Builder、C# 的 StringBuilder、JavaScript 的链式 API(jQuery、Lodash)——无处不在。
是什么
流式建造者(Fluent Builder / Method Chaining)是建造者模式的可读性进化版。它的核心思想是:通过 return this 让建造者的每个配置方法都返回自身,从而支持链式调用。
// 传统建造者:分步调用,冗长
Pizza pizza = new Pizza.Builder()
.setDough("thin")
.build();
pizza.setSauce("tomato");
pizza.setTopping("cheese");
// 流式建造者:一气呵成,像读自然语言
Pizza pizza = new Pizza.Builder()
.withDough("thin")
.withSauce("tomato")
.withTopping("cheese")
.withTopping("mushroom")
.build();
流式建造者不是语法糖,而是认知科学在 API 设计中的落地:人类工作记忆一次只能处理 7±2 个信息块,链式调用将多步配置压缩为一句连贯的"句子",大幅降低认知负荷。
几时出现的
- 2004 年:Martin Fowler 在《Fluent Interface》一文中正式命名"Fluent Interface",以 Java 的
StringBuilder(Java 1.5, 2004)为典型案例 - 2006 年:jQuery 发布,
$("#id").addClass("active").fadeIn().delay(500)将链式调用推向主流,成为 JavaScript API 设计的黄金标准 - 2010 年代:Lombok(2010)的
@Builder注解自动生成流式建造者;C# 的StringBuilder和 LINQ 方法链成为标准;RxJava/RxJS 的 Observable 链式操作将流式思想扩展到异步编程 - 2020 年代:几乎所有现代 API(从 ORM 到 HTTP 客户端到配置库)都提供流式接口作为默认或首选调用方式
哪里用的多
| 场景 | 典型 API | 链式调用示例 |
|---|---|---|
| 字符串构建 | Java StringBuilder | sb.append("a").append("b").append("c") |
| HTTP 请求 | OkHttp / Retrofit | client.newCall(request).enqueue(callback) |
| ORM 查询 | JPA Criteria / QueryDSL | query.select(entity).where(condition).orderBy(field) |
| 测试断言 | AssertJ / Hamcrest | assertThat(user).hasName("Tom").hasAge(25) |
| 配置构建 | Lombok @Builder / Immutables | User.builder().name("Tom").age(25).build() |
| 流式处理 | Java Stream / LINQ | list.stream().filter(x -> x > 0).map(x -> x * 2).collect(toList()) |
为什么不是 P7?
流式接口不是"语法糖领域专属"。它是建造者模式在可读性约束下的必然进化:当对象有 10 个可选参数时,new Builder().setA().setB().setC().build() 比 10 个参数的构造函数更符合人类认知。
与建造者模式的区别:
- 建造者:Director 调用 Builder 的方法,导演与建造者分离(如
director.construct()内部调用builder.buildPartA()) - 流式建造者:导演就是建造者本身,通过
return this实现链式调用,调用者自己"导演"构建过程
两者是 P1 在"可读性"约束下的两个层次。
推导链:
A1: 实体必然经历创建→存在→消亡
↓
约束: 复杂对象参数多,且创建过程需要符合人类语言的顺序阅读习惯
↓
P1-规则: 创建过程必须分步进行,且每步的调用方式符合自然语言的链式表达
↓
模式: 流式建造者(Fluent Builder / Method Chaining)
工业级案例
- Java StringBuilder:JDK 1.5 引入,替代线程安全的
StringBuffer,通过链式append()成为字符串拼接的标准方式 - Lombok @Builder:通过注解在编译期自动生成流式建造者代码,使 Java 开发者无需手写冗长的 Builder 样板代码
- OkHttp:HTTP 客户端的配置完全通过流式 API 完成,
new OkHttpClient.Builder().connectTimeout(10, SECONDS).build() - RxJava/RxJS:异步数据流的全部操作(map/filter/merge/delay)都是链式调用,将流式思想从对象创建扩展到数据变换
2.3 归入 P2 接口统一的 2 个新模式
2.3.1 动态代理 / AOP(Dynamic Proxy / Aspect-Oriented Programming)
流行度:⭐⭐⭐ 极高。Spring AOP、Castle DynamicProxy、Java 动态代理(java.lang.reflect.Proxy)、CGLIB——横切逻辑的工业标准。没有 AOP,企业级系统的日志、事务、权限、监控将淹没在业务代码中。
是什么
动态代理(Dynamic Proxy)和面向切面编程(Aspect-Oriented Programming, AOP)是代理模式(Proxy)的自动化进化版:
- 动态代理:在运行时通过反射或字节码技术,自动生成一个与原对象实现相同接口的代理类,无需手写代理代码
- AOP:在动态代理的基础上,将横切关注点(日志、事务、权限、缓存、监控)从业务代码中剥离,通过"切面"(Aspect)统一注入
核心思想:你写业务代码,框架在运行时自动帮你包一层代理,把横切逻辑织入进去。
// 你写的:纯业务代码
@Service
public class OrderService {
public void createOrder(Order order) { ... }
}
// Spring 在运行时自动做的:生成代理类,织入事务
public class OrderService$$SpringCGLIB$$0 extends OrderService {
public void createOrder(Order order) {
TransactionAspect.openTransaction(); // 自动织入
super.createOrder(order);
TransactionAspect.commit(); // 自动织入
}
}
几时出现的
- 1996 年:Gregor Kiczales 在 Xerox PARC 提出 AOP 概念,试图解决"横切关注点散射"问题
- 2001 年:AspectJ 1.0 发布,首个工业级 AOP 实现,通过编译期字节码织入实现切面
- 2004 年:Spring Framework 1.2 引入基于代理的 AOP(Spring AOP),与 AspectJ 注解兼容但实现更简单
- 2006 年:Java 动态代理(
java.lang.reflect.Proxy)成为 JDK 标准;CGLIB 通过字节码生成实现类代理(无需接口) - 2010 年代:AOP 思想渗透到所有框架——Spring Boot 的
@Transactional、ASP.NET Core 的 Middleware、Django 的装饰器、Rails 的 Concern - 2020 年代:AOP 从"显式切面"演化为"隐式代理"——服务网格(Service Mesh)通过 Sidecar 代理实现分布式 AOP,Kubernetes 的 Admission Webhook 实现基础设施级切面
哪里用的多
| 横切关注点 | AOP 实现 | 典型注解/API |
|---|---|---|
| 事务管理 | Spring AOP | @Transactional |
| 日志记录 | Spring AOP / LogAspect | @LogOperation |
| 权限控制 | Spring Security AOP | @PreAuthorize |
| 缓存 | Spring Cache AOP | @Cacheable |
| 性能监控 | Micrometer / Prometheus AOP | @Timed |
| 重试机制 | Spring Retry AOP | @Retryable |
| 限流熔断 | Sentinel / Hystrix AOP | @SentinelResource |
| 分布式追踪 | OpenTelemetry AOP | 自动织入 Trace ID |
为什么从 P7 回归 P2?
AOP 不是"框架领域专属"。它的核心逻辑与 Spring 无关——在运行时自动生成代理对象,保持接口一致性,同时注入横切逻辑。这是代理模式(P2)在技术演进后的自然形态。
- 代理模式(GoF):手写代理类,编译时确定,一个代理对应一个目标
- 动态代理/AOP:自动生成代理类,运行时确定,一个切面可以代理任意目标
两者的底层约束完全相同(A2 分层公理:需要在访问对象时添加额外控制,且保持接口一致),只是代理的生成方式从"人工"进化为"自动"。
与静态代理的区别:
- 静态代理:手写代理类,编译时确定,一对一(一个代理类代理一个具体类)
- 动态代理:运行时通过反射/字节码生成代理类,一对多(一个切面可以代理所有匹配的方法)
两者都是 P2 的"透明控制"规则,只是代理的创建时机和复用范围不同。
推导链:
A2: 复杂交互必然分层,层间通过接口通信
↓
约束: 需要在访问对象时自动添加横切逻辑(日志、事务、权限),且接口在编译时未知或频繁变化
↓
P2-规则: 必须创建与原对象具有相同接口的代理对象,但代理的生成过程自动化
↓
模式: 动态代理 / AOP(Dynamic Proxy / Aspect-Oriented Programming)
工业级案例
- Spring AOP:基于 JDK 动态代理(接口代理)和 CGLIB(类代理),通过
@Aspect注解定义切面,@Around/@Before/@After定义通知,实现声明式事务、缓存、日志 - Castle DynamicProxy:.NET 世界最成熟的动态代理库,NHibernate、Moq、FakeItEasy 都基于它实现代理
- gRPC / Envoy:服务网格通过 Sidecar 代理实现分布式 AOP——日志、追踪、认证、限流全部在 Sidecar 中完成,业务代码零侵入
- Java Agent + ByteBuddy:通过 JVM Agent 在类加载时修改字节码,实现无注解的透明 AOP(如 SkyWalking 的自动追踪)
2.3.2 仓储(Repository)
流行度:⭐⭐ 高。领域驱动设计(DDD)的核心模式,Spring Data、Entity Framework、Hibernate、Django ORM——数据访问层的标准抽象。没有仓储,业务逻辑将直接耦合 SQL 语句。
是什么
仓储模式(Repository Pattern)的核心思想是:为领域对象提供统一的集合接口,屏蔽底层持久化细节(SQL、NoSQL、文件、缓存),使业务逻辑只与"对象集合"打交道,不关心数据从哪来、怎么存。
// 仓储接口:对业务层暴露的是"集合操作"
public interface OrderRepository {
Order findById(Long id);
List<Order> findByCustomerId(Long customerId);
Order save(Order order);
void delete(Long id);
}
// 业务层:完全不知道底层是 MySQL、MongoDB 还是内存
@Service
public class OrderService {
private final OrderRepository repo; // 构造函数注入
public void cancelOrder(Long orderId) {
Order order = repo.findById(orderId); // 像操作内存集合一样操作数据库
order.cancel();
repo.save(order);
}
}
仓储不是 DAO(Data Access Object)。DAO 是数据表级别的抽象(一个 DAO 对应一张表),仓储是领域对象级别的抽象(一个仓储对应一个聚合根)。DAO 暴露的是 insert/update/delete,仓储暴露的是 find/save/remove——语义更接近内存集合。
几时出现的
- 2003 年:Eric Evans 在《领域驱动设计》(Domain-Driven Design)一书中正式定义仓储模式,作为"聚合根的持久化抽象"
- 2006 年:Spring Data JPA 的前身(Spring ORM + JPA)开始普及,仓储思想进入 Java 主流
- 2008 年:.NET Entity Framework 引入
DbSet<T>,本质上是仓储的框架级实现 - 2010 年代:Spring Data(2011)通过
JpaRepository、MongoRepository等接口将仓储模式标准化——开发者只需定义接口,框架自动生成实现 - 2020 年代:仓储思想渗透到所有现代 ORM 和数据库访问层,从关系型数据库扩展到 Elasticsearch、Redis、Neo4j 等异构存储
哪里用的多
| 框架/平台 | 仓储实现 | 典型用法 |
|---|---|---|
| Java/Spring | Spring Data JPA / MongoDB / Redis | interface UserRepo extends JpaRepository<User, Long> |
| .NET | Entity Framework Core | DbSet<Order> Orders |
| PHP/Laravel | Eloquent Repository Pattern | OrderRepository::find($id) |
| Python/Django | Django ORM Manager | Order.objects.filter(status='pending') |
| Node.js/NestJS | TypeORM / Prisma Repository | this.orderRepo.findOne({ where: { id } }) |
| Go | GORM / Ent | client.Order.Query().Where(...).All(ctx) |
为什么不是 P7?
仓储不是"DDD 领域专属"。任何需要屏蔽持久化细节、对外提供统一集合接口的系统都可以使用:文件系统、缓存、NoSQL、关系数据库。
- 外观模式(Facade):简化复杂子系统的入口(如简化一个包含 20 个类的报表子系统)
- 适配器模式(Adapter):转换不兼容接口(如把旧系统的 XML API 适配为新系统的 JSON API)
- 仓储:统一持久化接口 + 屏蔽存储细节——是 P2 在"数据持久化"场景下的自然延伸
与外观 + 适配器的关系:
- 外观:简化复杂子系统的入口
- 适配器:转换不兼容接口
- 仓储:统一持久化接口 + 屏蔽存储细节——是 P2 在"数据持久化"场景下的自然延伸
推导链:
A2: 复杂交互必然分层,层间通过接口通信
↓
约束: 业务逻辑需要操作对象集合,但持久化方式(SQL/NoSQL/文件)可能变化
↓
P2-规则: 必须提供统一接口屏蔽底层持久化细节,使业务层不依赖具体存储技术
↓
模式: 仓储(Repository)
工业级案例
- Spring Data JPA:开发者只需定义
interface OrderRepository extends JpaRepository<Order, Long>,Spring 自动生成 CRUD、分页、排序、动态查询的实现 - Entity Framework Core:
DbContext中的DbSet<T>就是仓储的框架级实现,context.Orders.Where(o => o.Status == "Pending").ToList()屏蔽了 SQL 生成细节 - CQRS 中的读仓储:在 CQRS 架构中,读模型和写模型使用不同的仓储实现(写模型用关系型数据库保证一致性,读模型用 Elasticsearch 优化查询),仓储接口统一了这种异构性
- 缓存仓储:
CachedOrderRepository装饰OrderRepository,先查 Redis 再查数据库——仓储接口的一致性使得缓存层可以透明插入
2.4 归入 P4 行为可变的 1 个新模式
2.4.1 空对象(Null Object)
流行度:⭐⭐ 高。Collections.emptyList()、NoOpLogger、默认策略——消灭 if (obj != null) 的利器。每一个防御性编程的 if (x != null) 都是空对象模式的候选替换者。
是什么
空对象模式(Null Object Pattern)的核心思想是:"什么都不做"是一种合法的行为策略,它应该被封装为与其他行为相同的接口,而不是通过 null 值和条件检查来特殊处理。
// 没有空对象:到处防御性编程,代码臃肿且容易遗漏
public void log(String message) {
if (logger != null) { // 每次调用都要检查!
logger.log(message);
}
}
// 有空对象:无需检查,直接调用
public void log(String message) {
logger.log(message); // logger 永远不会是 null,它可能是 NoOpLogger
}
// 空对象实现:和其他 Logger 实现相同的接口,但方法体为空
public class NoOpLogger implements Logger {
@Override
public void log(String message) {
// 什么都不做——这就是它的"行为"
}
}
空对象不是"偷懒",而是将"无操作"提升为一等公民——它和其他策略一样,实现相同的接口,遵循相同的契约,只是语义是"跳过"。
几时出现的
- 1997 年:Bobby Woolf 在《The Null Object Pattern》一文中正式命名该模式,作为 GoF 策略模式的特例
- 2004 年:Java 5 引入
Collections.emptyList()/emptyMap()/emptySet(),空对象思想进入标准库 - 2008 年:SLF4J 的
NOPLogger(No-Operation Logger)成为 Java 日志框架中空对象的典范 - 2010 年代:函数式编程的
Option/Maybe类型(ScalaNone、HaskellNothing、RustNone)与空对象模式思想相通——"空"不是异常,是一种合法状态 - 2020 年代:空对象思想渗透到所有现代 API:Java 的
Stream.empty()、JavaScript 的noop函数、React 的Fragment、Go 的io.Discard
哪里用的多
| 场景 | 空对象实现 | 消灭的 null 检查 |
|---|---|---|
| 日志系统 | NoOpLogger、NOPLogger |
if (logger != null) logger.log(...) |
| 集合操作 | Collections.emptyList() |
if (list != null) for (item : list) |
| 动画/回调 | NoOpAnimationListener |
if (listener != null) listener.onStart() |
| 策略选择 | NoOpStrategy(默认策略) |
if (strategy != null) strategy.execute() |
| 图形渲染 | NullShape、NullBrush |
if (shape != null) shape.draw() |
| 配置读取 | NullConfigSource |
if (config != null) config.getString(...) |
为什么不是 P7?
空对象不是"函数式编程专属"。它的核心逻辑极其纯粹:"什么都不做"是一种合法的行为策略。
在 P4(行为可变范式)的框架下:
- 策略模式:算法族可以互相替换,客户端选择其中一个
- 空对象:策略族中包含一个"什么都不做"的策略,客户端无需知道它选的是"真策略"还是"空策略"
空对象的价值不在于"它做了什么",而在于**“它消灭了 null 检查”**——将运行时的 NullPointerException 风险转化为编译时的类型安全。
与策略模式的关系:
- 空对象是策略模式的特例——策略族中包含一个"什么都不做"的策略
- 但它独立成模式的价值在于:消灭
if (obj != null)的防御性编程,将 null 从"异常状态"提升为"合法行为"
推导链:
A4: 同一结构在不同上下文下必然表现出不同行为
↓
约束: 某些操作在特定条件下需要"什么都不做",但客户端不应频繁进行空值检查
↓
P4-规则: "无操作"必须封装为与其他行为相同的接口,作为行为变体之一
↓
模式: 空对象(Null Object)
工业级案例
- Java Collections Framework:
Collections.emptyList()、emptyMap()、emptySet()返回不可变的空集合,避免返回 null 导致调用方 NPE - SLF4J NOPLogger:当项目中没有绑定具体日志实现时,SLF4J 自动使用 NOPLogger(No-Operation Logger),业务代码无需知道日志是否启用
- Android NoOpAnimatorListener:动画监听器有四个方法(onStart/onEnd/onCancel/onRepeat),如果只需要监听一个,继承 NoOpAnimatorListener 只需重写一个方法
- Spring NullMailSender:在测试环境中,用 NullMailSender 替代真实邮件发送器,所有
send()调用静默忽略,测试代码无需 mock
2.5 归入 P5 信息流转的 1 个新模式
2.5.1 事件总线 / Pub-Sub(Event Bus / Publish-Subscribe)
流行度:⭐⭐⭐ 极高。Android EventBus、RxJS、Node.js EventEmitter、Kafka、RabbitMQ、Redis Pub/Sub——系统级解耦的标准方案。从微服务到前端组件通信,事件总线是"解耦"的代名词。
是什么
事件总线(Event Bus)/ 发布-订阅(Publish-Subscribe)模式的核心思想是:引入一个中央中介(事件总线),发布者(Publisher)不直接持有订阅者(Subscriber)的引用,而是将事件发送到总线;订阅者向总线注册自己感兴趣的事件类型;总线负责将事件分发给所有匹配的订阅者。
// 没有事件总线:发布者直接持有观察者引用,耦合在对象级
class OrderService {
private List<OrderObserver> observers = new ArrayList<>();
public void createOrder(Order order) {
// ... 创建订单
for (OrderObserver o : observers) o.onOrderCreated(order); // 直接调用
}
}
// 有事件总线:发布者和订阅者完全解耦,通过事件名间接通信
class OrderService {
private EventBus eventBus;
public void createOrder(Order order) {
// ... 创建订单
eventBus.publish(new OrderCreatedEvent(order)); // 发给总线,不关心谁接收
}
}
// 订阅者:向总线注册,不关心事件从哪来
class InventoryService {
@Subscribe // 或 @EventListener
public void onOrderCreated(OrderCreatedEvent event) {
// 扣减库存
}
}
事件总线与观察者模式的本质区别在于耦合粒度:
- 观察者模式:Subject 直接持有 Observer 的引用列表,对象级耦合(Subject 知道 Observer 的接口类型)
- 事件总线:Publisher 和 Subscriber 通过**事件名/主题(Topic)**间接耦合,系统级解耦(Publisher 不知道谁订阅了,Subscriber 不知道谁发布了)
几时出现的
- 1987 年:观察者模式(Observer Pattern)在 GoF 成书前已在 Smalltalk 中广泛使用,但仅限于对象级的一对多通知
- 2003 年:Enterprise Integration Patterns(Hohpe & Woolf)正式定义"Publish-Subscribe Channel",将 Pub-Sub 提升为企业集成模式
- 2007 年:Node.js 发布,内置
EventEmitter模块,emitter.on('event', callback)将事件总线思想带入前端/服务端 JavaScript - 2010 年:Android 开源库 EventBus(greenrobot)发布,
@Subscribe注解让 Android 组件间通信彻底解耦 - 2012 年:RxJava/RxJS 发布,Observable 流将事件总线升级为"响应式事件流",支持过滤、变换、合并、背压
- 2014 年:Apache Kafka 成为大数据领域的事实标准事件总线,支持高吞吐、持久化、分区的事件发布-订阅
- 2020 年代:事件驱动架构(EDA)成为微服务标准,EventBridge、NATS、Redis Pub/Sub、RabbitMQ 都是事件总线的工业实现
哪里用的多
| 领域 | 事件总线实现 | 典型场景 |
|---|---|---|
| 前端开发 | RxJS、Vue Event Bus、Redux Actions | 组件间通信、状态变更通知 |
| Android | greenrobot EventBus、LiveData | Activity/Fragment/Service 间解耦通信 |
| Java 后端 | Spring ApplicationEvent、Axon Framework | 领域事件发布、 Saga 编排 |
| 微服务 | Kafka、RabbitMQ、NATS | 跨服务异步通信、事件溯源 |
| 游戏开发 | Unity EventManager、Unreal Delegates | 游戏事件系统(击杀、拾取、任务完成) |
| 物联网 | MQTT Broker(Pub/Sub 协议) | 设备状态上报、指令下发 |
为什么不是 P7?
事件总线不是"分布式领域专属"。一个进程内的 EventBus(如 Android EventBus、Node.js EventEmitter)同样是事件总线。它的核心逻辑与网络无关——一对多广播拓扑在系统级的实现。
- 观察者模式(GoF):微观层面的一对多通知,Subject 和 Observer 在同一个模块内
- 事件总线:宏观层面的系统级广播,Publisher 和 Subscriber 可以跨模块、跨服务、甚至跨进程
两者是 P5(信息流转范式)在不同规模约束下的两个层次:观察者是"微观",事件总线是"宏观"。
与观察者模式的区别:
- 观察者:Subject 直接持有 Observer 引用,对象级耦合(Subject 必须知道 Observer 的接口)
- 事件总线:发布者和订阅者通过事件名/主题间接耦合,系统级解耦(Publisher 不知道谁订阅了,Subscriber 不知道谁发布了)
推导链:
A5: 信息传递必然有拓扑,拓扑决定耦合度
↓
约束: 一个事件需要通知多个订阅者,但订阅者数量动态变化且可能跨模块
↓
P5-规则: 信息流转拓扑应为"一对多"发布-订阅,且订阅关系动态管理
↓
模式: 事件总线 / 发布-订阅(Event Bus / Pub-Sub)
工业级案例
- Kafka:高吞吐分布式事件总线,支持分区、持久化、消费者组,成为微服务事件驱动架构的事实标准
- RxJS:前端响应式编程的核心,Observable 流将事件总线升级为可组合、可变换、可背压控制的异步数据流
- Spring ApplicationEvent:Spring 框架内置的事件总线,
ApplicationEventPublisher.publishEvent()实现模块间解耦通信 - MQTT:物联网领域的标准 Pub/Sub 协议,Broker 作为事件总线,设备作为 Publisher/Subscriber,实现低功耗、高并发的消息分发
2.6 归入 P3 × P5 交叉推导的 1 个新模式
2.6.1 中间件管道 / 拦截器链(Middleware Pipeline / Interceptor Chain)
流行度:⭐⭐⭐ 极高。.NET Core app.UseXxx()、Express app.use()、Servlet Filter、Koa 洋葱模型、Spring Interceptor、Django Middleware——Web 框架的基石。没有中间件管道,就没有现代 Web 框架的可扩展架构。
是什么
中间件管道(Middleware Pipeline)/ 拦截器链(Interceptor Chain)的核心思想是:将请求的处理过程拆分为一系列独立的处理单元(中间件),每个单元实现统一的接口,按固定顺序串联成链;请求从链头进入,依次经过每个中间件的处理,最终到达目标处理器;响应沿链反向返回,每个中间件可以在返回路径上执行后置处理。
// Express 中间件管道:每个中间件处理请求,然后调用 next() 传递给下一个
app.use(loggingMiddleware); // 第1步:记录请求日志
app.use(authenticationMiddleware); // 第2步:验证用户身份
app.use(authorizationMiddleware); // 第3步:检查权限
app.use(rateLimitMiddleware); // 第4步:限流检查
app.use('/api/orders', orderHandler); // 第5步:业务处理
// 每个中间件的统一结构
function loggingMiddleware(req, res, next) {
console.log(`${req.method} ${req.url}`); // 前置处理
next(); // 调用下一个中间件
console.log(`Response: ${res.statusCode}`); // 后置处理(next 返回后执行)
}
中间件管道的核心特征是**“洋葱模型”:请求像穿过一层层洋葱皮,进入时逐层前置处理,返回时逐层后置处理。每个中间件必须**调用 next()(或等效机制)将控制权传递给下一个节点——这与责任链(“匹配则终止”)和装饰器(“嵌套调用”)都不同。
几时出现的
- 1998 年:Java Servlet 2.3 规范引入
Filter链,doFilter(request, response, chain)成为最早的工业级中间件管道实现 - 2004 年:Ruby on Rails 引入
before_filter/after_filter,将拦截器思想带入 MVC 框架 - 2009 年:Node.js + Express 发布,
app.use()将中间件管道推向主流,成为 JavaScript Web 开发的黄金标准 - 2010 年:Spring 3.1 引入
HandlerInterceptor接口,preHandle/postHandle/afterCompletion三阶段拦截成为 Java 生态的标准 - 2016 年:.NET Core 发布,中间件管道作为框架核心架构,
IApplicationBuilder.Use()成为所有 .NET Web 应用的起点 - 2017 年:Koa 2.0 发布,
async/await中间件将洋葱模型推向极致,中间件可以等待下游完成后再执行后置逻辑 - 2020 年代:中间件管道思想渗透到所有框架——从 HTTP 请求处理到 GraphQL Resolver 到 gRPC 拦截器到消息队列消费者
哪里用的多
| 框架/平台 | 中间件实现 | 典型中间件 |
|---|---|---|
| Node.js/Express | app.use(middleware) |
日志、CORS、BodyParser、认证、限流 |
| Node.js/Koa | app.use(async (ctx, next) => { ... }) |
错误处理、JWT 验证、响应压缩 |
| .NET Core | app.UseXxx() |
路由、认证、静态文件、异常处理、HSTS |
| Java/Spring | HandlerInterceptor / Filter |
登录校验、权限检查、日志记录、事务 |
| Python/Django | MIDDLEWARE 列表 |
CSRF 保护、认证、消息框架、点击劫持保护 |
| Python/FastAPI | app.add_middleware() |
CORS、GZip、TrustedHost、认证 |
| gRPC | UnaryInterceptor / StreamInterceptor |
认证、日志、重试、监控 |
| GraphQL | Schema Directive / Middleware |
权限校验、数据加载、缓存 |
为什么不是 P7?
中间件管道不是"Web 领域专属"。任何需要按序处理 + 动态增删处理步骤的系统都可以使用:日志管道、数据处理管道、编译器前端、音频处理链、图像滤镜链。
- 装饰器模式(Decorator):动态叠加功能,结构是嵌套树(A 装饰 B,B 装饰 C,形成
A(B(C))) - 责任链模式(Chain of Responsibility):请求逐级传递,直到被处理(某个节点处理后就停止传递)
- 中间件管道:每个节点都处理请求,且必须调用 next() 继续传递——是装饰器(全部执行)和责任链(线性传递)的交叉产物
与装饰器 + 责任链的关系:
- 装饰器:动态叠加功能,结构是嵌套树
- 责任链:请求逐级传递,直到被处理
- 中间件管道:每个节点都处理请求,且必须调用 next() 继续传递——是装饰器(全部执行)和责任链(线性传递)的交叉产物
推导链:
A3: 结构必然由单元组合而成,组合操作封闭
A5: 信息传递必然有拓扑
↓
约束: 请求需要经过一系列处理步骤,步骤可动态增删重排
↓
P3-规则: 每个处理单元实现统一接口,可递归组合(A3)
P5-规则: 信息按固定顺序流经处理单元链(A5)
↓
交叉推导: 中间件管道 / 拦截器链(Middleware Pipeline / Interceptor Chain)
工业级案例
- .NET Core 中间件管道:
IApplicationBuilder的Use()、Map()、Run()方法构建完整的请求处理管道,从 Kestrel 接收 HTTP 请求到路由到 Controller 到返回响应,全程由中间件链处理 - Express 中间件生态:
body-parser、cors、helmet、morgan、compression等数千个中间件包,通过app.use()动态组合,无需修改框架源码 - Spring Security Filter Chain:Spring Security 的全部功能(认证、授权、CSRF、CORS、会话管理)都通过
SecurityFilterChain实现,开发者可以自定义 Filter 插入到任意位置 - gRPC Interceptor:客户端和服务端的拦截器链统一处理元数据、认证、重试、监控,实现跨语言的横切逻辑注入
- Koa 洋葱模型:
async/await让中间件可以"等待"下游全部处理完成后再执行后置逻辑(如统计响应时间、记录最终状态码),这是传统同步中间件无法实现的
2.7 仍然留在 P7 的(真正的领域叠加)
以下模式虽然也很流行,但它们的推导必须依赖领域公理,无法从 A1–A5 直接导出:
| 模式 | 为什么只能是 P7 | 领域公理 |
|---|---|---|
| 断路器 Circuit Breaker | 需要"网络调用可能失败"的分布公理 | 分布公理 A:网络不可靠 |
| Saga 模式 | 需要"分布式事务需要异步协调"的分布公理 | 分布公理 C:长事务协调 |
| CQRS | 需要"读/写负载分离"的分布公理 + 性能约束 | 分布公理 B:CAP 权衡 |
| 事件溯源 Event Sourcing | 需要"状态=事件序列折叠"的时序公理 | 时序公理:状态可追溯 |
| 读写锁 | 需要"读可并行、写需独占"的并发公理 | 并发公理 B:原子性序列 |
| MVC/MVP/MVVM | 需要"界面展示逻辑与业务逻辑变化频率不同"的 UI 公理 | UI 公理:变化频率差异 |
这些模式的共同特征是:去掉领域约束(网络、并发、时序、UI),它们就不存在。而前面纳入的 8 个模式,去掉领域约束后仍然存在——它们是纯公理的必然产物。
第三章:边界声明——体系不覆盖什么
3.1 边界声明的必要性
公理化体系的尊严,来自于它对自己边界的诚实。
欧几里得几何不覆盖非欧几何,牛顿力学不覆盖量子力学,但它们在自己的边界内是完备的、严密的、有用的。5-7-2 框架的价值不在于"覆盖一切",而在于:
在"面向对象系统结构设计"这个边界内,从 5 条不可再约的存在公理出发,可以严密推导出 31 个核心模式,且推导链的每一步都有工程判据校验。
超出这个边界,请使用其他工具。
3.2 与框架推导关系极弱 / 毫无关系的 4 类模式
即使在纯 OOP 领域内,仍有 4 类模式与 5-7-2 框架坐标系不同,无法被推导。
3.2.1 第一类:元模型层模式——需要额外的"类型-身份"公理
5-7-2 框架建立在**“类/类型是设计时的静态结构”**这一隐含假设之上。以下模式挑战的正是这个假设:
ECS(Entity-Component-System)
- 流行度:⭐⭐⭐ 游戏开发绝对主流(Unity DOTS、Unreal Mass、Bevy)
- 核心:Entity = 纯 ID,Component = 纯数据,System = 纯行为
- 为什么无关:ECS 撕毁了 OOP 的核心契约(数据与行为封装在一起)。A1 的"实体"是有行为的对象,ECS 的 Entity 没有行为;A3 的"组合"是对象树,ECS 的 Component 是平铺数组;A4 的"行为可变"不存在,行为在 System 中。
- 结论:ECS 不是"OOP 模式",它是用 OOP 语言实现的非 OOP 架构(Data-Oriented Design)。
Type Object(类型对象)
- 核心:用实例替代子类(
new Monster(MonsterType.DRAGON)) - 为什么无关:5-7-2 不覆盖**“类型层级如何扁平化”**的问题。A1–A5 假设了"类/类型"是设计时的静态结构,不是运行时可替换的数据。
- 结论:需要额外的"类型即数据"公理。
Role Object(角色对象)
- 核心:对象在运行时动态附加 Role(一个人同时是员工、客户、管理员)
- 为什么无关:A4 假设"一个对象有一个当前行为",Role Object 假设"一个对象有多个并行的行为集合"。
- 结论:需要额外的"身份-角色分离"公理。
3.2.2 第二类:谓词逻辑层模式——需要"谓词可组合"公理
Specification(规格模式)
- 核心:将业务规则封装为可组合的对象(
isGoldMember AND orderAmount > 1000) - 为什么关系牵强:结构上看像 P3(递归组合),但语义完全不同。P3 的组合是"物理结构的组合"(文件夹包含文件),Specification 的组合是"逻辑谓词的组合"(布尔代数)。
- 结论:需要额外的"谓词可组合"公理(来自命题逻辑/布尔代数),这不是 A1–A5 的内容。
3.2.3 第三类:纯实现惯用法——不是问题,是手法
以下模式不是"设计模式"(Design Patterns),而是**“编程惯用法”**(Idioms)。GoF 明确区分了这两者:模式解决"反复出现的设计问题",惯用法解决"特定语言的实现技巧"。
| 惯用法 | 本质 | 与 5-7-2 的关系 |
|---|---|---|
| RAII | C++ 资源获取即初始化 | 生命周期绑定语法技巧,不是 A1 的创建策略 |
| Pimpl | 编译防火墙 | 编译期头文件隔离,不是 A2 的运行时分层 |
| CRTP | C++ 静态多态 | 模板元编程技巧,没有运行时对象结构 |
| 双分派实现技巧 | 多态分发优化 | 访问者模式的实现手段,不是独立设计问题 |
3.2.4 第四类:特定技术栈的架构约定——上下文太重
以下模式在特定技术栈中极其重要,但把它们从特定技术栈中抽离,它们就不存在了。而 5-7-2 框架中的模式是技术栈无关的。
| 模式 | 技术上下文 | 为什么无关 |
|---|---|---|
| BFF(Backend for Frontend) | 微服务 + 多端适配 | 组织架构与技术栈的耦合产物,不是 A2 的抽象分层 |
| Sidecar | Kubernetes / 服务网格 | 进程级部署模式,不是代理模式的对象级接口控制 |
| Redux / Flux | React 前端生态 | 前端状态管理约定,不是 P4 的状态模式(没有状态类封装行为) |
| GraphQL Resolver | API 层 | 查询解析与执行的绑定方式,不是解释器模式(语法是固定的) |
3.3 边界总图
┌─────────────────────────────────────────────────────────────┐
│ 软件工程全领域 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 组织/流程层 │ Scrum, Kanban, DevOps, 康威定律 │
│ (不覆盖) │ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 算法/数据层 │ MapReduce, Fork-Join, Pipeline, ETL │
│ (不覆盖) │ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 语言/语法层 │ RAII, Pimpl, CRTP, async/await │
│ (不覆盖) │ │
├─────────────────────────────────────────────────────────────┤
│ │
│ 技术/架构层 │ BFF, Sidecar, Redux, GraphQL Resolver │
│ (部分覆盖) │ (仅当抽象为通用结构时,如事件总线可归 P5) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 其他范式层 │ Monad, Lens, Unification, Continuation │
│ (不覆盖) │ (FP/LP/过程式有自己的公理系统) │
├─────────────────────────────────────────────────────────────┤
│ │
│ 元模型层 │ ECS, Type Object, Role Object, Specification │
│ (不覆盖) │ (需要额外的"类型-身份-谓词"公理) │
├─────────────────────────────────────────────────────────────┤
│ │
│ ★ 本框架覆盖 │ 对象结构设计模式:创建、结构、行为、信息流转 │
│ ★ 的边界 │ (31 个核心模式,5-7-2 框架内完备) │
│ │
└─────────────────────────────────────────────────────────────┘
第四章:完备性证明与自洽性
4.1 完备性的定义
在 5-7-2 框架中,完备性定义为:
对于任何面向对象系统设计问题,如果其约束条件可以被 A1–A5 中的至少一条公理所描述,那么其最优解必然落在 P1–P7 中的某个范式之内,且该范式内的具体模式可以通过场景细分唯一确定(在工程判据 V1/V2 的校验下)。
4.2 完备性的证明思路
4.2.1 公理的穷尽性
A1–A5 覆盖了 OOP 系统设计的全部基本维度:
| 设计问题 | 对应公理 | 说明 |
|---|---|---|
| 对象从哪来、怎么创建 | A1 | 实体化公理 |
| 对象之间如何交互、如何分层 | A2 | 分层公理 |
| 对象如何组合成更大结构 | A3 | 组合公理 |
| 对象的行为如何变化 | A4 | 行为可变公理 |
| 对象之间如何传递信息 | A5 | 信息拓扑公理 |
任何 OOP 系统设计问题,必然涉及上述至少一个维度。如果一个问题不涉及任何维度(例如"如何优化 SQL 查询"),则它不在本框架的边界内。
4.2.2 范式的封闭性
对于每个公理维度,P1–P5 提供了该维度下的完备模式族:
- A1 → P1:创建控制的全部策略(唯一实例、委托创建、分步创建、复制创建、预创建复用、外部容器管理、链式可读创建)
- A2 → P2:接口统一的全部策略(适配、桥接、简化、代理、动态代理、持久化抽象)
- A3 → P3:递归组合的全部策略(部分-整体、功能叠加、共享复用)
- A4 → P4:行为可变的全部策略(状态驱动、外部选择、骨架固定、无操作变体)
- A5 → P5:信息流转的全部策略(广播、星型中转、链式传递、对象化封装、系统级解耦)
P6(结构操作)覆盖了 A3 和 A4 的交叉场景:结构稳定后的操作外置。
P7(领域叠加)覆盖了基础范式在特定领域约束下的演化。
4.2.3 判据的过滤性
V1/V2 确保推导结果在工程上可用:
- V1 拦截职责不集中的推导(如上帝创建器)
- V2 拦截扩展需要修改源码的推导(如简单工厂 switch-case)
4.3 自洽性检验
自洽性要求:体系内部不存在矛盾。
检验一:公理独立性
A1–A5 之间不存在推导关系:
- A1(实体生命周期)不蕴含 A2(分层)
- A2(分层)不蕴含 A3(组合)
- A3(组合)不蕴含 A4(行为可变)
- A4(行为可变)不蕴含 A5(信息拓扑)
检验二:范式完备性
P1–P7 覆盖了 A1–A5 的所有组合:
- 单公理:P1–P5
- 双公理交叉:P6(A3 × A4)
- 全部公理 + 领域:P7
检验三:模式无遗漏
31 个核心模式覆盖了 OOP 结构设计的主流场景,且每个模式有明确的范式归属。
检验四:判据不矛盾
V1 和 V2 之间不存在矛盾:一个结构可以同时满足高内聚和高演化性(如策略模式)。
4.4 不完备性的诚实声明
5-7-2 框架不完备于以下问题:
- 非 OOP 架构(ECS、函数式架构)
- 元模型设计(类型对象、角色对象)
- 谓词逻辑组合(规格模式)
- 算法与数据结构(MapReduce、Pipeline)
- 组织与流程(Scrum、DevOps)
这些不完备不是缺陷,而是边界——就像欧几里得几何不覆盖非欧几何,但它在欧氏空间内是完备的。
附录 A:5 分钟速览
A.1 5 条公理(记住关键词)
| 公理 | 关键词 |
|---|---|
| A1 实体化 | 对象从哪来、怎么没 |
| A2 分层 | 复杂必然分层 |
| A3 组合 | 结构可拆可组 |
| A4 行为可变 | 行为随上下文变 |
| A5 信息拓扑 | 信息传递有拓扑 |
A.2 7 个范式(记住对应关系)
| 范式 | 来源 | 关键词 |
|---|---|---|
| P1 创建控制 | A1 | 对象创建 |
| P2 接口统一 | A2 | 层间接口 |
| P3 递归组合 | A3 | 结构组合 |
| P4 行为可变 | A4 | 行为切换 |
| P5 信息流转 | A5 | 消息传递 |
| P6 结构操作 | A3 × A4 | 结构稳定、操作外置 |
| P7 领域叠加 | A1~A5 + 领域 | 基础范式 × 领域约束 |
A.3 2 个判据(记住作用)
- V1 内聚性:职责是否单一?
- V2 演化性:扩展是否需要改源码?
A.4 使用流程
- 遇到设计问题 → 判断属于哪个公理维度
- 定位对应范式 → 在范式内选择具体模式
- V1/V2 校验 → 是否工程可用?
附录 B:31 模式完整归属总表
| 范式 | 范式名称 | 推导来源 | 包含模式 | 模式数 | 累计 |
|---|---|---|---|---|---|
| P1 | 创建控制范式 | A1 | 单例、工厂方法、抽象工厂、建造者、原型、对象池、DI/IoC、流式建造者 | 8 | 8 |
| P2 | 接口统一范式 | A2 | 适配器、桥接、外观、代理、动态代理/AOP、仓储 | 6 | 14 |
| P3 | 递归组合范式 | A3 | 组合、装饰器、享元 | 3 | 17 |
| P4 | 行为可变范式 | A4 | 状态、策略、模板方法、空对象 | 4 | 21 |
| P5 | 信息流转范式 | A5 | 观察者、中介者、责任链、命令、事件总线/Pub-Sub | 5 | 26 |
| P6 | 结构操作范式 | A3 × A4 | 访问者、迭代器、备忘录、中间件管道 | 4 | 30 |
| P7 | 领域叠加范式 | A1~A5 + 领域公理 | 解释器、MVC/MVP/MVVM、并发/分布式族 | 1+ | 31+ |
总计:31 个核心模式(23 GoF + 8 新增)
附录 C:新旧体系三版对照
| 维度 | 原书第一版 | 增补修正版 | 完备性宣言 |
|---|---|---|---|
| 公理 | 7 条(含 2 条万能横切) | 5 条核心 + 2 个判据 | 同增补修正版 |
| 范式 | 6 个(含 1 个双核心混杂 + 1 个剩余类别) | 7 个(5 基础 + 1 交叉 + 1 叠加) | 同增补修正版 |
| 模式 | 23 GoF | 23 GoF | 31 个(+8 新增) |
| 边界 | 未明确声明 | 初步声明 | 明确声明 4 类不覆盖模式 |
| 完备性 | 未讨论 | 未讨论 | 给出完备性定义与证明思路 |
全书完
三本书共同构成一个完整的认知闭环:
- 《23个设计模式精讲全集》:自下而上,建立模式直觉
- 《设计模式公理化推演体系》:自上而下,构建公理逻辑
- 《完备性宣言》:精炼体系,纳入新知,明确边界
愿每一位读者都能从"知道模式"走向"理解模式",最终达到"推演模式"的境界——并清楚知道,推演之外,还有广阔天地。
——作者
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)