Skip to main content

ddd

封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。

Source facts

Repository
microwind/ai-skills
Last source activity
May 25, 2026 at 23:32
Detected SKILL.md language
Chinese
Stars
83
Forks
24

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
DDD 工厂
description
封装复杂对象和聚合的创建过程,将创建职责从领域对象中剥离,保证聚合创建时的不变量满足。
# 工厂 (Factory) ## 概述 工厂是 DDD 战术设计的一个构建块,源于 GoF 设计模式中的工厂模式。Eric Evans 在蓝皮书中保留这一概念的动机是:当对象或聚合的创建过程足够复杂,让对象自己"生自己"会污染领域模型时,把创建逻辑独立成工厂——**让构造即合法**。 **核心动机**: > 将创建复杂对象和聚合的职责赋予一个独立的对象。这个对象本身不承担领域模型的业务职责,但仍是领域设计的一部分。工厂应提供一个清晰的创建接口,封装创建过程的复杂性,并确保聚合一经创建即满足其业务不变量。 > ⚠️ **工程务实**:**独立工厂类是少数场景**——大多数聚合用**构造函数 + 不变量校验**或**聚合根上的静态工厂方法**就足够。只有当创建过程需要外部依赖(仓储、策略、外部服务)或涉及多步骤组装时,才升级为独立工厂类。不要为了"看起来更 DDD"而为简单聚合包一层空壳工厂。 ## 何时使用工厂 ``` ✅ 需要工厂的场景: ▸ 创建逻辑涉及多个步骤或外部数据 ▸ 聚合内部包含复杂的对象图(多层嵌套的实体和值对象) ▸ 需要保证创建时的不变量(例如订单必须至少有一个商品行) ▸ 构造函数参数过多,难以理解 ▸ 对象的初始状态需要根据上下文计算 ❌ 不需要工厂的场景: ▸ 简单对象:直接用构造函数即可 ▸ 无任何不变量的值对象:直接 new 没问题 ▸ 工厂只是构造函数的"套壳" ``` ## 工厂的三种形态 ### 1. 独立工厂类(Factory Class) 当创建逻辑复杂、依赖外部服务时使用。 ```java public class OrderFactory { private final ProductRepository productRepo; private final DiscountPolicy discountPolicy; public OrderFactory(ProductRepository productRepo, DiscountPolicy discountPolicy) { this.productRepo = productRepo; this.discountPolicy = discountPolicy; } public Order create(CustomerId customerId, List<OrderLineSpec> specs) { if (specs == null || specs.isEmpty()) { throw new DomainException("订单必须至少有一个商品"); } OrderId orderId = OrderId.generate(); List<OrderLine> lines = specs.stream() .map(spec -> { Product product = productRepo.findById(spec.productId()) .orElseThrow(() -> new DomainException("商品不存在")); Money unitPrice = discountPolicy.applyTo(product.getPrice(), customerId); return new OrderLine(product.getId(), unitPrice, spec.quantity()); }) .toList(); Order order = new Order(orderId, customerId, lines); return order; } } ``` ### 2. 聚合根上的工厂方法(Static Factory Method) 适用于大多数常规聚合。 ```java public class Subscription { private final SubscriptionId id; private final ReaderId reader; private final ColumnId column; private final Instant startTime; private SubscriptionStatus status; private Subscription(SubscriptionId id, ReaderId reader, ColumnId column, Instant startTime) { this.id = id; this.reader = reader; this.column = column; this.startTime = startTime; this.status = SubscriptionStatus.ACTIVE; } public static Subscription fromPayment(Payment payment, Column column) { if (!payment.isCompleted()) { throw new DomainException("只有支付完成才能创建订阅"); } if (payment.getAmount().compareTo(column.getPrice()) < 0) { throw new DomainException("支付金额不足"); } return new Subscription( SubscriptionId.generate(), payment.getPayerId(), column.getId(), payment.getCompletedAt() ); } } ``` ### 3. 值对象工厂(Value Object Factory) 对构造逻辑不平凡的值对象做一层封装。 ```java public class Money { private final BigDecimal amount; private final Currency currency; private Money(BigDecimal amount, Currency currency) { ... } public static Money of(BigDecimal amount, Currency currency) { if (amount.scale() > currency.getDefaultFractionDigits()) { throw new DomainException("精度超过货币允许范围"); } return new Money(amount.setScale(currency.getDefaultFractionDigits(), RoundingMode.HALF_EVEN), currency); } public static Money yuan(long yuan) { return of(BigDecimal.valueOf(yuan), Currency.getInstance("CNY")); } public static Money zero(Currency currency) { return of(BigDecimal.ZERO, currency); } } ``` ## Python 示例 ```python from dataclasses import dataclass from typing import List @dataclass(frozen=True) class OrderLineSpec: product_id: str quantity: int class OrderFactory: def __init__(self, product_repo, discount_policy): self.product_repo = product_repo self.discount_policy = discount_policy def create(self, customer_id: str, specs: List[OrderLineSpec]) -> "Order": if not specs: raise DomainError("订单必须至少有一个商品") lines = [] for spec in specs: product = self.product_repo.find_by_id(spec.product_id) if not product: raise DomainError(f"商品不存在: {spec.product_id}") unit_price = self.discount_policy.apply(product.price, customer_id) lines.append(OrderLine(product.id, unit_price, spec.quantity)) return Order(id=OrderId.generate(), customer_id=customer_id, lines=lines) ``` ## 工厂 vs 构造函数 vs 仓储 ``` ┌─────────────┐ ┌─────────────────┐ ┌─────────────────────┐ │ Constructor │ │ Factory │ │ Repository │ ├─────────────┤ ├─────────────────┤ ├─────────────────────┤ │ 简单对象创建 │ │ 复杂对象创建 │ │ 重建已存在的聚合 │ │ 参数少 │ │ 多步骤/多依赖 │ │ 从存储加载数据 │ │ 无外部依赖 │ │ 可能访问仓储/服务 │ │ 负责持久化 │ └─────────────┘ └─────────────────┘ └─────────────────────┘ 新建 (create) ─▶ Factory 重建 (reconstitute) ─▶ Repository 简单值对象 ─▶ Constructor ``` ## 最佳实践 ### 1️⃣ 工厂不能产生违反不变量的对象 ```java // ❌ 工厂只是组合字段,不校验 public static Order create(CustomerId customerId, List<OrderLine> lines) { return new Order(customerId, lines); // lines 为空也能过! } // ✅ 工厂保证不变量 public static Order create(CustomerId customerId, List<OrderLine> lines) { if (lines == null || lines.isEmpty()) { throw new DomainException("订单必须至少有一个商品"); } if (lines.size() > MAX_LINES) { throw new DomainException("商品行数超限"); } return new Order(OrderId.generate(), customerId, lines); } ``` ### 2️⃣ 工厂属于领域层 工厂是领域设计的一部分,**定义在领域层**,与聚合同 package。不要放到应用服务或基础设施层。 ``` domain/ └── order/ ├── Order.java ← 聚合根 ├── OrderLine.java ← 实体 ├── OrderFactory.java ← 工厂(同层) └── OrderRepository.java ← 仓储接口 ``` ### 3️⃣ 优先使用聚合根静态方法 对于大部分场景,**聚合根的静态工厂方法**就够了,不需要独立工厂类: ```java // 大多数场景 Order order = Order.create(customerId, lines); // 仅当创建需要外部依赖(查商品、算折扣)时,才用独立工厂类 Order order = orderFactory.create(customerId, lineSpecs); ``` ### 4️⃣ 工厂方法名要表达意图 ```java // ❌ 抽象的 create / build Subscription.create(payment); // ✅ 表达业务语义 Subscription.fromPayment(payment); // 因支付而产生 Subscription.giftFrom(author, reader); // 作者赠予 Subscription.trialFor(reader, days); // 试用订阅 ``` ## 常见误区 ❌ **工厂只是构造函数的"套壳"**——没有任何不变量校验或业务语义 → 工厂的价值是"构造即合法"或封装复杂创建;否则直接用构造函数 ❌ **工厂放到应用层 / 基础设施层**——破坏依赖方向 → 工厂属于领域层,与聚合同 package ❌ **抽象的方法命名**——一律 `create()` / `build()`,丢失业务意图 → 用业务语义命名:`Subscription.fromPayment()`、`Order.placeFromCart()`、`Money.yuan(100)` ## 与其他 DDD 概念的关系 | 概念 | 关系 | |------|------| | [聚合](../aggregate/) | 工厂的主要用途是创建聚合 | | [聚合根设计](../aggregate-root-design/) | 聚合根上的静态工厂方法是首选形态 | | [实体](../entity/) | 工厂负责为实体分配身份标识 | | [仓储](../repository/) | 仓储负责"重建"已有聚合,工厂负责"新建"聚合 | | [领域服务](../domain-service/) | 创建涉及多个聚合协作时,常以领域服务形式出现工厂 | ## 总结 **核心**:把复杂的对象 / 聚合创建过程从领域模型中独立出来,同时保证创建时的业务不变量。 **实践**: - 简单对象 → 构造函数 - 常规聚合 → 聚合根静态工厂方法 - 复杂创建(依赖仓储 / 服务 / 策略)→ 独立工厂类 - 已存在聚合的加载 → 仓储,不是工厂 **关键**:工厂的名字应表达业务意图,工厂必须保证聚合创建即合法。
View on GitHub