⌨️ 第五章 从设计到测试
📄 本篇摘要

DDD(领域驱动设计)=让业务专家和程序员说同一门话:代码里的类名、方法名直接用业务词汇,模型即代码、代码即模型。值对象是 DDD 的第一块积木:没有身份、不可变、按值相等——【100 元就是 100 元】,跟哪张钞票无关。

📝 关系

上一节:AOP 与 DI | 下一节:构造与析构

一、DDD 的病根诊断:两套语言

传统分工的现场:业务专家说【核销、账期、过账】,程序员翻译成【process()、update_flag()】——每层翻译都是失真源,需求理解的偏差藏在翻译里。DDD 的主张:通用语言(Ubiquitous Language)——业务词汇直接进代码:

业务专家说的            代码里就叫
申请报销                 apply_reimbursement
审批通过                 approve
金额                     Money

模型改了代码跟着改,代码看不懂就是模型没对齐——对话、文档、代码三样说一门话。

二、限界上下文:同一个词,不同地盘

【商品】在【下单】语境里管价格库存,在【物流】语境里管重量体积——同一个词不同含义。DDD 用限界上下文(Bounded Context)划地盘:每个上下文里模型自洽,上下文之间明确接口。教材点到为止:别追求一个全局大模型——按业务边界切块,块内通用语言才真正通用。

三、值对象:没有身份证的类

领域对象分两种:

值对象三特征:不可变(改=造新的)、按值判等、自带业务规则(负数金额直接拒)。

四、动手 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. **限界上下文解决什么问题?** 答:同一个词在不同业务里含义不同——按业务边界切块,块内语言自洽。

相关笔记