工厂方法 = 不变量建立器
解决什么问题:复杂对象的创建逻辑容易泄露不变量—— 客户端需要知道 ID 生成、状态初始化、依赖校验、事件发布,才能"正确"创建一个对象。工厂把这一切封装为单一入口。
业务问题
真实场景
场景 1:电商订单创建的"5 步初始化"
python
# 反模式:客户端拼装
order = Order()
order.id = generate_id() # 1. ID 生成
order.tenant_id = tenant.id # 2. 租户绑定
order.customer_id = customer.id # 3. 客户绑定
order.created_at = now() # 4. 时间戳
order.status = "NEW" # 5. 状态初始化
order.add_event(OrderCreated(...)) # 6. 事件发布
# 问题:
# - 漏一步 = 数据不一致
# - 多处复制 = 修改要改 N 处
# - 不变量(订单必须有客户)必须由客户端保证Vernon Ch 11 引自 Evans 原话:
"Shift the responsibility for creating instances of complex objects and AGGREGATES to a separate object... Create entire AGGREGATES as a piece, enforcing their invariants."
失败模式
| 失败模式 | 后果 |
|---|---|
| 客户端拼装 | 漏一步 = 数据不一致;客户端承担业务规则 |
| 构造函数复杂 | 测试困难;子类化困难 |
| 工厂无业务规则 | 工厂退化为"传话筒" |
| 工厂依赖过多 | 违反 SRP;难以测试 |
控制论视角
Ch 4 §4.6:"没有反馈,就没有自动控制"——同样,没有自动校验,就没有一致的数据。
工厂 = 创建时的不变量自动建立:
创建前:状态不确定(参数可能错误)
↓
工厂方法:
- 校验参数 → 不变量 1
- 校验依赖 → 不变量 2
- 初始化 → 不变量 3
- 发布事件 → 状态变更通知
↓
创建后:状态确定(不变量已建立)对位:
- 工厂 = 控制律的初始化阶段(状态从任意 → 受控)
- 不变量 = 受控状态的稳定条件(类比"设定点 setpoint")
- 事件 = 状态变更的输出信号
核心概念
工厂的 3 种形式
| 形式 | 位置 | 适用 | Vernon 推荐度 |
|---|---|---|---|
| Factory Method on Aggregate | 聚合根的方法 | 简单场景 | ★★★★★ 最常用 |
| Factory on Domain Service | 领域服务 | 跨聚合创建 | ★★★ |
| Standalone Factory | 独立类 | 复杂组装 | ★★ |
最常用:Factory Method on Aggregate。
Factory Method on Aggregate(标准模式)
python
class Order:
def __init__(self, id, tenant_id, customer_id, status, created_at):
# 构造函数是私有的(不对外暴露)
self.id = id
self.tenant_id = tenant_id
self.customer_id = customer_id
self.status = status
self.created_at = created_at
self._events = []
@classmethod
def create(cls, tenant_id, customer_id):
# 1. 生成身份(不变量:必须有 ID)
order_id = OrderId.generate()
# 2. 校验不变量(不变量:必须有客户)
if not customer_id:
raise BusinessRuleViolation("Customer required")
# 3. 初始化状态(不变量:状态必须是 NEW)
order = cls(order_id, tenant_id, customer_id, "NEW", now())
# 4. 发布事件(状态变更通知)
order.add_event(OrderCreated(order_id, customer_id))
return order关键原则:
- 工厂方法命名用业务语言(create, register, schedule)
- 内部封装所有不变量检查
- 返回的对象保证不变量已满足
- 客户端不需要做任何校验
Factory as Domain Service(跨聚合场景)
python
class TenantRegistrationService:
def register(self, tenant_info):
# 跨聚合创建:Tenant + 初始用户 + 初始角色
tenant = Tenant.create(tenant_info)
admin_user = User.create_admin(tenant.id, tenant_info.admin_email)
admin_role = Role.assign_admin(admin_user.id)
# 全部原子操作
self.tenant_repo.save(tenant)
self.user_repo.save(admin_user)
self.role_repo.save(admin_role)
# 发布事件
self.event_publisher.publish(TenantRegistered(tenant.id))Standalone Factory(复杂组装)
python
class ProductFactory:
"""复杂产品创建:需要多个步骤、多种配置"""
def create_bundle(self, bundle_spec):
products = []
for spec in bundle_spec.items:
product = Product.create(spec)
products.append(product)
return ProductBundle(
products=products,
discount=bundle_spec.discount,
valid_until=bundle_spec.valid_until
)工程实践
决策 1:何时用工厂
问 3 个问题:
1. 创建过程 > 3 步?
- 是 → 用工厂
2. 创建需要保证不变量?
- 是 → 用工厂
3. 多个创建变体?
- 是 → 用工厂(封装变体)决策 2:选哪种工厂
决策树:
创建需要其他聚合数据?
- 是 → 领域服务工厂
- 否 → 聚合根工厂
聚合根工厂的实现复杂?
- 是 → 独立工厂类
- 否 → 简单类方法反模式 vs 正模式
| 反模式 | 正模式 |
|---|---|
| 简单对象也用工厂 | 复杂对象(>3 步)才用 |
| 工厂只做"传话筒" | 工厂封装业务逻辑 |
| 构造函数公开(客户端可直接 new) | 构造函数私有/受保护 |
| 工厂依赖太多服务 | 工厂依赖最小化 |
与已有 L 层的关系
- 聚合 4 规则:聚合的创建必须通过工厂,强制不变量
- 实体与值对象:实体用工厂方法,值对象用构造函数
- 应用服务 vs 领域服务:工厂方法在领域层,不在应用层
性能指标
| 指标 | 衡量 | 健康值 |
|---|---|---|
| 工厂覆盖度 | 复杂对象中工厂占比 | > 90% |
| 工厂的复杂度 | 工厂方法平均行数 | < 30 行 |
| 不变量在工厂的覆盖率 | 不变量由工厂保证的比例 | > 95% |
| 工厂重用度 | 工厂被多少处调用 | > 1 |
边界判断
何时用工厂(★ 必须)
- 创建过程 > 3 步
- 必须保证不变量
- 多个创建变体
- 跨聚合创建
何时用构造函数(★ 不要工厂)
- 简单值对象(1-2 步)
- 无业务规则
- 单一变体
何时升级
| 当前 | 升级到 | 触发条件 |
|---|---|---|
| Factory Method | Domain Service Factory | 跨聚合 |
| Domain Service Factory | Standalone Factory | 组装 > 50 行 |
关键洞察
- 工厂 = 不变量建立器——这是工厂在 DDD 中的核心价值,不只是"创建对象"。
- 工厂方法 = 状态初始化——把"任意状态 → 受控状态"的转换封装为单一入口。
- 工厂应该薄——工厂只做"建立不变量",不做"业务规则"。
- 测试友好——客户端不需要构造完整参数,只需要传业务参数。
- 与 cybernetics 同构:控制律的"初始条件建立" + DDD 的"不变量建立" = 同一个数学原型。