41

我不明白为什么 DDD 中的值对象应该是不可变的,我也看不出这是如何轻松完成的。(我专注于 C# 和实体框架,如果这很重要的话。)

例如,让我们考虑经典的 Address 值对象。如果您需要将“123 Main St”更改为“123 Main Street ”,为什么我需要构造一个全新的对象而不是说 myCustomer.Address.AddressLine1 = “123 Main Street”?(即使实体框架支持结构,这仍然是个问题,不是吗?)

我理解(我认为)值对象没有身份并且是域对象的一部分的想法,但是有人可以解释为什么不变性是一件好事吗?


编辑:我在这里的最后一个问题真的应该是“有人可以解释为什么应用于值对象的不变性是一件好事吗?” 对困惑感到抱歉!


编辑:为了澄清,我不是在询问 CLR 值类型(与引用类型相比)。我在问值对象的更高级别的 DDD 概念。

例如,这是一种为实体框架实现不可变值类型的 hack 方式: http ://rogeralsing.com/2009/05/21/entity-framework-4-immutable-value-objects 。基本上,他只是将所有二传手设为私有。为什么要这样做呢?

4

6 回答 6

56

忽略所有关于线程安全等的疯狂答案,这与 DDD 无关。(我还没有看到线程安全的 O/R 映射器或其他对 DDD 友好的 dal)

想象一个权重的值对象。假设我们有一个 KG 值对象。

示例(为清楚起见进行了编辑):

var kg75 = new Weight(75);
joe.Weight = kg75;
jimmy.Weight = kg75;

现在如果我们这样做会发生什么:

jimmy.Weight.Value = 82;

如果我们仍然使用相同的对象引用,那也会改变 joe 的权重。请注意,我们为 joe 和 jimmy 分配了一个代表 75kg 的对象。当 jimmy 体重增加时,不是 kg75 的对象发生了变化,而是 jimmy 的体重发生了变化,因此,我们应该创建一个代表 82 kg 的新对象。

但是,如果我们有一个新会话并将 joe 和 jimmy 加载到一个干净的 UoW 中怎么办?

 var joe = context.People.Where(p => p.Name = "joe").First();
 var jimmy = context.People.Where(p => p.Name = "jimmy").First();
 jimmy.Weight.Value = 82;

那时会发生什么?好吧,因为在您的情况下 EF4 会加载 joe 和 jimmy 以及它们的权重而没有任何身份,我们会得到两个不同的权重对象,当我们更改 jimmys weight 时,joe 的重量仍然与以前相同。

因此,对于相同的代码,我们会有两种不同的行为。如果对象引用仍然相同,那么 joe 和 jimmy 都会获得一个新的权重。如果 joe 和 jimmy 以干净的 uow 加载,则只有其中一个会受到更改的影响。

这将是相当不一致的 imo。

通过使用不可变 VO,您将在两种情况下获得相同的行为,并且在构建对象图时,您仍然可以重用对象引用以减少内存占用。

于 2011-01-03T07:33:16.147 回答
43

为什么 6 是不可变的?

理解这一点,你就会明白为什么值对象应该是不可变的。

编辑:我现在将我们的对话提升到这个答案中。

6是不可变的,因为6的身份取决于它所代表的内容,即拥有六个事物的状态。你不能改变那6代表那。现在,这是值对象的基本概念。它们的价值取决于它们的状态。然而,一个实体不是由它的状态决定的。ACustomer可以更改他们的姓氏或地址,但仍然是相同的Customer。这就是为什么值对象应该是不可变的。他们的状态决定了他们的身份;如果他们的状态改变,他们的身份也应该改变。

于 2011-01-03T02:13:53.573 回答
15

我参加聚会很晚了,但我自己一直在想这个。(将不胜感激任何评论。)

我认为这里没有明确引用它,但我认为 Evans 对不变性的引用主要是在共享的背景下:

为了安全地共享对象,它必须是不可变的:除非完全替换,否则不能更改它。(埃文斯 p100)

Evan 的书中还有一个侧边栏,名为“地址是值对象吗?谁在问?”。

如果室友各自打电话订购电力服务[即如果两个客户有相同的地址],公司就需要意识到这一点。[所以] 地址是一个实体。(埃文斯 p98)

在您给出的示例中,假设客户的家庭地址和公司地址都是 123 Main Street。当您进行描述的更正时,两个地址都会改变吗?如果是这样,并且如果我正确地阅读了 Evans,那么听起来你真的有一个实体。

举一个不同的例子,假设我们有一个对象来表示客户的全名:

public class FullName
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class Customer
{
    public FullName Name { get; set; }
}

如果没有值对象,以下操作将失败:

[Test]
public void SomeTest() {
    var fullname = new FullName { FirstName = "Alice", LastName = "Jones" };
    var customer1 = new Customer { Name = fullname };
    var customer2 = new Customer { Name = fullname };

    // Customer 1 gets married.
    customer1.Name.LastName = "Smith";

    // Presumably Customer 2 shouldn't get their name changed.
    // However the following will fail.
    Assert.AreEqual("Jones", customer2.Name.LastName);
}

就优势而言,一般都是在 DDD 中挑选出来的,值对象的实际优势是什么?. 值得注意的是,您只需在创建时验证 VO。如果你这样做,那么你知道它总是有效的。

于 2011-02-08T08:32:54.080 回答
5

这可能不是完整的答案。我只是在回答您关于不变性优势的问题。

  1. 因为不可变对象是线程安全的。由于它们无法更改状态,因此它们不会被线程干扰破坏或观察到不一致的状态。
  2. 对不可变对象的引用可以很容易地共享或缓存,而无需复制或克隆它们,因为它们的状态在构造后永远无法更改。
  3. 有关不可变性的更多优点,请看这里(LBushkin 的回答) 字符串不可变的优点是什么?

这是Martin Fowler 关于为什么值对象应该是不可变的示例。

好吧,虽然将 VO 设为不可变并不是强制性的(即使 DDD 书籍也没有说它们必须是不可变的),但在 DDD 中,使其成为 VO 的主要思想似乎不是处理生命周期的复杂性,例如实体。在这里查看更多详细信息

于 2011-01-03T02:04:43.523 回答
0

值对象需要是不可变的。

在许多情况下,不可变对象确实让生活变得更简单。...并且他们可以使并发编程方式更安全,更清洁以获取更多信息

让我们认为值对象是可变的。

class Name{
string firstName,middleName,lastName
....
setters/getters
}

假设你原来的名字是 Richard Thomas Cook

现在假设您只更改了 firstName(to Martin) 和 lastName(to Bond),如果它不是不可变对象,您将使用方法一个一个地改变状态。在Martin Thomas Bond的最终名称之前的那个 Aggregate 状态中的名字是Martin Thomas Cook的机会 是绝对不能接受的(这也给后来看代码的人带来了错误的想法,导致在进一步的设计中产生不良的多米诺骨牌效应)。

可变值对象明确地必须对 1 个事务中给出的更改强制执行完整性约束,该事务在不可变对象中是免费提供的。因此,使值对象不可变是有意义的。

于 2016-11-03T14:52:56.360 回答
0

很久以前就有人问过这个问题,但我决定用一个我觉得简单易记的例子来提供答案。此外,SO 可以作为许多开发人员的参考,我认为遇到这个问题的任何人都可以从中受益。

因为它们是由它们的属性定义的,所以值对象被视为不可变的

价值对象的一个​​很好的例子是金钱。即使您无法区分口袋里同样的五张一美元钞票,这也没关系。你不关心货币的身份——只关心它的价值和它代表什么。如果有人用你钱包里的一张五美元钞票换了一张,这不会改变你还有五美元的事实。

因此,例如,在 C# 中,您将 money 定义为不可变的值对象:

public class Money
{
    protected readonly decimal Value;

    public Money(decimal value)
    {
        Value = value;
    }

    public Money Add(Money money)
    {
        return new Money(Value + money.Value);
    }

    // ...


    // Equality (operators, Equals etc) overrides (here or in a Value Object Base class). 
    // For example:

    public override bool Equals(object obj)
    {
        return Equals(obj as Money);
    }

    public bool Equals(Money money)
    {
        if (money == null) return false;
        return money.Value == Value;
    }
}
于 2018-08-01T21:00:12.033 回答