DDD(领域驱动设计)=让业务专家和程序员说同一门话:代码里的类名、方法名直接用业务词汇,模型即代码、代码即模型。值对象是 DDD 的第一块积木:没有身份、不可变、按值相等——【100 元就是 100 元】,跟哪张钞票无关。
一、DDD 的病根诊断:两套语言
传统分工的现场:业务专家说【核销、账期、过账】,程序员翻译成【process()、update_flag()】——每层翻译都是失真源,需求理解的偏差藏在翻译里。DDD 的主张:通用语言(Ubiquitous Language)——业务词汇直接进代码:
业务专家说的 代码里就叫
申请报销 apply_reimbursement
审批通过 approve
金额 Money
模型改了代码跟着改,代码看不懂就是模型没对齐——对话、文档、代码三样说一门话。
二、限界上下文:同一个词,不同地盘
【商品】在【下单】语境里管价格库存,在【物流】语境里管重量体积——同一个词不同含义。DDD 用限界上下文(Bounded Context)划地盘:每个上下文里模型自洽,上下文之间明确接口。教材点到为止:别追求一个全局大模型——按业务边界切块,块内通用语言才真正通用。
三、值对象:没有身份证的类
领域对象分两种:
- 实体(Entity):有身份——两个【波仔】的账号即使信息全同也不是同一人(按 ID 区分);
- 值对象(Value Object):没有身份,按值相等——
Money(100, "CNY")就是 100 元,管它是哪张钞票。
值对象三特征:不可变(改=造新的)、按值判等、自带业务规则(负数金额直接拒)。
四、动手 Demo:Money 值对象的三特征
import hashlib
class Money:
def __init__(self, amount, currency="CNY"):
if amount < 0:
raise ValueError("金额不能为负") # 自带业务规则
self._amount = amount
self._currency = currency
@property
def amount(self):
return self._amount
def add(self, other):
if other._currency != self._currency:
raise ValueError("币种不同不能直接加")
return Money(self._amount + other._amount, self._currency) # 不可变:返回新对象
def __eq__(self, other): # 按值相等
return (isinstance(other, Money)
and self._amount == other._amount
and self._currency == other._currency)
def __hash__(self):
return hash((self._amount, self._currency))
def __repr__(self):
return f"Money({self._amount}, {self._currency})"
price = Money(100)
fee = Money(20)
total = price.add(fee)
print("总价:", total, "| 原 price 不变:", price) # 不可变:原对象没被动过
print("按值相等:", Money(100) == Money(100)) # 没有身份证:值同即等
print("集合去重:", len({Money(50), Money(50), Money(99)}))
try:
Money(-5)
except ValueError as e:
print("业务规则拦截:", e)
沙箱实跑输出:
总价: Money(120, CNY) | 原 price 不变: Money(100, CNY)
按值相等: True
集合去重: 2
业务规则拦截: 金额不能为负
看到什么算成功:相加产生新对象且原值不变;两个 100 元相等;集合里两个 Money(50) 去重成一个;负金额被拦。
读结果:与 5-15 的 Dog 对比——Dog 按身份区分(两只同名狗是两只狗),Money 按值区分(两个 100 元是一样多)。判等、哈希、不可变三者配套:改一个忘一个就是 bug 温床。业务规则(不许负数)内置在构造里,规则只写一处——通用语言落到了代码。
五、30 秒实验
给 Money 加 multiply(factor) 返回新 Money——注意同样不改 self。再试试在 add 里写 self._amount += other._amount; return self(可变版)——price 就被污染了。值对象的可变性是全局污染的开端。
六、常见误区
| 误区 | 真相 |
|---|---|
| DDD=用新框架 | DDD 是建模方法论;框架可有可无,语言对齐才是核心 |
| 所有对象都做成值对象 | 有身份的(用户、订单)必须做实体按 ID 区分;两者各有岗位 |
| 通用语言=把术语表发给大家 | 要落进代码:类名方法名就是术语表;翻译层消失才算落地 |
自测三问(点击展开)
1. **实体和值对象的分界?** 答:有没有身份——实体按 ID 判等(账号),值对象按值判等(金额)。 2. **值对象三特征?** 答:不可变(改=造新)、按值相等、自带业务规则。 3. **限界上下文解决什么问题?** 答:同一个词在不同业务里含义不同——按业务边界切块,块内语言自洽。- ☐ 5-24 学习完成
相关笔记
- 类、实例与对象 —— 值对象是类的特化用法
- 字段、方法与属性 —— 不可变与 property 的配合
- ⌨️ 编程原理 · 索引 —— 全课路线图