You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

遵循DDD实践,值对象的实现是否应密封?以Money类为例

领域驱动设计中值对象是否应该密封?

这是个非常贴合DDD实践细节的好问题!直接给结论:是的,你应该把Money这类值对象的具体实现类设置为sealed(密封)。下面从DDD设计原则和代码实践两个层面给你拆解原因:

一、贴合值对象的核心特性

DDD里的值对象是“值优先”的无身份对象,它的唯一标识就是自身的所有属性值,而且行为和属性定义是完整、不可变的。如果允许值对象被继承:

  • 子类可能新增属性或修改行为,破坏值对象的相等性逻辑(比如两个Money子类实例,基础属性完全一致,但子类新增的属性不同,会被判断为不相等,这和业务上“同值即相等”的预期冲突);
  • 值对象本身不需要多态扩展,业务上的不同值类型(比如Money和Address)应该是独立的类,而非继承关系。密封类能从设计上杜绝这种不必要的扩展。

二、保护值对象的创建与约束逻辑

看你的Money代码,构造函数是private的——这显然是为了通过工厂方法(比如后续会加的Create方法)严格控制实例的创建,确保只有合法的Money能被生成。如果不密封:

  • 即使子类无法访问父类的私有构造,也可能有人试图通过奇怪的方式绕开你的约束;
  • 密封类能彻底锁死这个类的继承可能性,让你的创建逻辑完全可控。

三、优化相等性判断的准确性与性能

通常ValueObject<T>的相等性判断会依赖GetType()来确保类型一致(比如other.GetType() == GetType())。如果Money是密封类:

  • 可以确保不会有子类混入相等性判断,避免出现“看似同值但类型不同(子类)”的误判;
  • C#的JIT编译器会对密封类的类型检查做优化,提升相等性判断的性能。

补充代码示例(添加sealed)

public sealed class Money : ValueObject<Money> { 
    private Money() { } 
    private Money(decimal value, string currency) { 
        Requires.NotEmpty(currency, nameof(currency)); 
        Requires.That(value >= 0, $"{nameof(value)} must be greater or equals to 0."); 
        Value = value; 
        Currency = currency;
    }

    // 典型的工厂方法,控制实例创建
    public static Money Create(decimal value, string currency) => new Money(value, currency);

    public decimal Value { get; }
    public string Currency { get; }

    // 实现ValueObject的核心相等性逻辑
    protected override bool EqualsCore(Money other)
    {
        return Value == other.Value && Currency == other.Currency;
    }

    protected override int GetHashCodeCore()
    {
        return HashCode.Combine(Value, Currency);
    }
}

总的来说,密封值对象是DDD实践里的一个最佳实践,它能帮你维护值对象的纯洁性,避免后续出现意料之外的设计漏洞。

内容的提问来源于stack exchange,提问作者Dmytro Zhluktenko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 04:14:52