值对象
表示描述性的、欠缺身份的概念
比如"余额",在大多数领域中,你会单独查询"余额"吗?不会,因为独立的"余额"没有任何意义,他必须附属于某一个实体才会拥有自身的概念
增强明确性
DDD的一切都是为了明确传递重要业务规则和领域逻辑,如果用字符串、整型这类基元类型的话,并不适合描述概念,所以,应该组合基元类型,并封装成明确表示其正在建模的高内聚对象
比较
尽管值对象没有身份,比较他们也是是至关重要的操作。通过他们的特性或价值来比较相等性
富含行为
值对象应该尽可能多的公开具有表述性的面向领域行为并封装状态。默认来说,所有基元类型都应该是私有的或受保护的(实体也是如此) 如:public string Name{get; private set;} ,只有当有足够合理的理由时,才能打破封装让其变成公共的
自验性
值对象必须保证状态有效(实体也是如此),它们自身就要单独负责确保满足这一需求,当创建值对象实例时,如果参数与规则不一致则构造函数里必须抛出异常。如Money可能会有两个领域规则:1.精确到两位小数。 2.不能为负数
不可变
一旦创建则不可变,每次修改都是返回新实例,因为不可变性通常更易于推断,并且天然属于线程安全,它只有很少的危险意外影响。并且不可变对象性能更好,因为为了保证对象状态完整性(避免数据撕裂)一般都会加锁,而不可变对象由于每次都返回新实例,永远都是数据完整的,所以不用加锁操作,不过这也是缺点,当每次对象/集合操作都会返回个新值,而旧值依旧会保留一段时间,这会使内存有极大开销,也会给GC造成回收负担,如果某个场景需要在两种方式选择其一的话,一般情况还是返回新对象为优先选择。.NET里的DateTime就是一个不可变性的经典示例。不可变性还会支持可组合性:组合起来以创建新的值,如Money + Money = new Money
public class Money : ValueObject<Money> { protected readonly decimal Value; public Money() : this( 0m ) { } /// <summary> /// 构造函数创建,当创建逻辑简单时应用构造函数创建 /// </summary> public Money( decimal value ) { if( value % 0.01m != 0 ) { throw new Exception( "必须精确到两位小数" ); } if( value < 0 ) { throw new Exception( "不能为负数" ); } Value = value; } /// <summary> /// 工厂创建,当创建逻辑复杂时应用工厂创建 /// </summary> public static Money Create( decimal value ) { if( value % 0.01m != 0 ) { throw new Exception( "必须精确到两位小数" ); } if( value < 0 ) { throw new Exception( "不能为负数" ); } return new Money( value ); } public Money Add( Money money ) { return new Money( Value + money.Value ); } public Money Subtract( Money money ) { return new Money( Value - money.Value ); } public static Money operator +( Money left , Money right ) { return new Money( left.Value + right.Value ); } public static Money operator -( Money left , Money right ) { return new Money( left.Value - right.Value ); } /// <summary> /// 值对象比较,基于特性 /// </summary> protected override IEnumerable<object> GetAttributesToIncludeInEqualityCheck() { return new List<object>() { Value }; } } class Program { static void Main( string[] args ) { var m = new Money( 100 ); var m2 = new Money( 50 ); var result = m - m2; var result2 = m + m2; } }