DDD中的值对象 - 为什么不可变?

时间:2011-01-03 01:55:10

标签: domain-driven-design value-objects

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

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

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


编辑:我的最后一个问题应该是“有人可以解释为什么不变性是一件好事 应用于价值对象 ?”抱歉混乱!


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

例如,这是一种为实体框架实现不可变值类型的黑客方法:http://rogeralsing.com/2009/05/21/entity-framework-4-immutable-value-objects。基本上,他只是让所有的安装者都私密。为什么要经历这样做的麻烦?

6 个答案:

答案 0 :(得分:42)

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

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

样本(为清晰起见而编辑):

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

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

jimmy.Weight.Value = 82;

如果我们仍然使用相同的对象引用,那也会改变joe的权重。 请注意,我们为joe和jimmy分配了一个代表75千克的对象。 当jimmy增加体重时,不是kg75对象已经改变,它是jimmys体重已经改变,因此,我们应该创建一个代表82公斤的新物体。

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

 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的重量时,joe仍然会像以前一样重量。

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

那将是非常不稳定的imo。

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

答案 1 :(得分:33)

为什么6不可变?

理解这一点,你就会理解为什么Value Objects应该是不可变的。

编辑:我现在将对话框提到这个答案。

6是不可变的,因为6的身份是由它代表的东西决定的,即有六个东西的状态。您无法更改6表示的内容。现在,这是Value Objects的基本概念。他们的价值取决于他们的国家。但是,实体不是由其州决定的。 Customer可以更改其姓氏或地址,但仍然是Customer。这就是Value Objects应该是不可变的原因。他们的国家决定他们的身份如果他们的状态发生变化,他们的身份应该改变。

答案 2 :(得分:10)

我参加晚会很晚,但我自己一直在想这个。 (感谢任何评论。)

我认为这里没有明确引用,但我认为埃文斯对不变性的提及主要是在分享的背景下:

  

为了安全地共享对象,它必须是不可变的:它   除非完全更换,否则无法更改。 (Evans p100)

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

  

如果室友都打电话订购电气服务[即如果两个客户有相同的地址],公司会   需要实现它。 [所以]地址是一个实体。 (埃文斯p98)

在您给出的示例中,假设客户的家庭和公司地址均为123 Main Street。当您进行描述校正时,两个地址都会发生变化吗?如果是这样,如果我正确地阅读埃文斯,听起来你真的有一个实体。

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

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);
}

就一般优势而言,有些是在In DDD, what are the actual advantages of value objects?获得的。值得注意的是,您只需要在创建时验证VO。如果你这样做,那么你知道它总是有效的。

答案 3 :(得分:5)

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

  1. 因为Immutable对象是线程 安全。既然他们无法改变 国家,他们不能被腐败 线程干扰或观察到的 不一致的状态。
  2. 对不可变的引用 对象可以很容易地共享或 缓存而无需复制或 克隆他们的状态不可能 在施工后永远改变了。
  3. 获得Immutability的更多优势 你看这里(LBushkin的答案) What's the advantage of a String being Immutable?
  4. Martin Fowler的

    This is an example关于为什么值对象应该是不可变的。

    好吧,虽然没有强制要求VO成为不可变的(即使DDD书并没有说它们必须是不可变的),DDD中使其成为VO的主要想法似乎不是要处理生活循环复杂性,如实体的复杂性。看这里for more details

答案 4 :(得分:0)

值对象需要是不可变的。

  

在许多情况下,不可变对象确实使生活变得更简单。 ......他们可以使并发编程更加安全和清洁for more info

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

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

让我们说你原来的名字是 Richard Thomas Cook

现在让我们假设您只更改firstName(对Martin)和lastName(对Bond), 如果它不是不可变对象,您将使用这些方法逐个改变状态。有名字的机会 在 Martin Thomas Bond 的最终名称之前, Martin Thomas Cook 处于该聚合状态是不可接受的(同时它也给了那个人错误的想法稍后查看代码,导致在进一步的设计中出现不希望的多米诺骨牌效应)。

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

答案 5 :(得分:0)

这是很久以前问过的,但是我决定提供一个示例,我觉得它很简单也很容易记住。此外,SO可以为许多开发人员提供参考,我认为任何遇到此问题的人都可以从中受益。

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

价值对象的一个​​很好的例子是金钱。没关系,您也无法在口袋中分辨出相同的五张一美元钞票。您不在乎该货币的身份,仅在乎其价值及其代表什么。如果有人用一张5美元的钞票换成您钱包里的5美元钞票,这不会改变您仍然还有5美元的事实。

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

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;
    }
}