域驱动设计,域对象,关于Setters的态度

Bud*_*Joe 11 .net oop domain-driven-design domain-model cqrs

最近一直在观看一些Greg Young视频,我试图理解为什么对Setters on Domain对象持否定态度.我认为在DDD中,Domain对象应该是"重"的逻辑.这个坏例子在网上是否有任何好的例子然后他们纠正它的方法?任何例子或解释都是好的.这仅适用于以CQRS方式存储的事件,还是适用于所有DDD?

Lou*_*ier 11

我正在贡献这个答案,以补充Roger Alsing以其他原因回答不变量.

语义信息

罗杰清楚地解释说,财产制定者不携带语义信息.允许像Post.PublishDate这样的属性的setter可能会增加混乱,因为我们无法确定帖子是否已发布或仅在发布日期已设置时.我们无法确定这是发布文章所需的全部内容.对象"界面"没有清楚地显示其"意图".

不过,我相信像"启用"这样的属性确实为"获取"和"设置"提供了足够的语义.它应该是立即有效的,我不能看到需要SetActive()或Activate()/ Deactivate()方法,原因仅在于属性设置器上缺少语义.

不变

罗杰还谈到了通过财产制定者打破不变量的可能性.这是绝对正确的,并且应该使得能够串联工作的属性提供"组合不变值"(作为使用Roger示例的三角形的角度)作为只读属性并创建一个方法来将它们全部设置在一起,这可以在一个步骤中验证所有组合.

属性顺序初始化/设置依赖项

这类似于不变量的问题,因为它会导致应该一起验证/更改的属性出现问题.想象一下以下代码:

    public class Period
    {
        DateTime start;
        public DateTime Start
        {
            get { return start; }
            set
            {
                if (value > end  && end != default(DateTime))
                    throw new Exception("Start can't be later than end!");
                start = value;
            }
        }

        DateTime end;
        public DateTime End
        {
            get { return end; }
            set
            {
                if (value < start && start != default(DateTime))
                    throw new Exception("End can't be earlier than start!");
                end = value;
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

这是导致访问顺序依赖性的"setter"验证的简单示例.以下代码说明了此问题:

        public void CanChangeStartAndEndInAnyOrder()
        {
            Period period = new Period(DateTime.Now, DateTime.Now);
            period.Start = DateTime.Now.AddDays(1); //--> will throw exception here
            period.End = DateTime.Now.AddDays(2);
            // the following may throw an exception depending on the order the C# compiler 
            // assigns the properties. 
            period = new Period()
            {
                Start = DateTime.Now.AddDays(1),
                End = DateTime.Now.AddDays(2),
            };
            // The order is not guaranteed by C#, so either way may throw an exception
            period = new Period()
            {
                End = DateTime.Now.AddDays(2),
                Start = DateTime.Now.AddDays(1),
            };
        }
Run Code Online (Sandbox Code Playgroud)

因为我们无法在句点对象上更改结束日期之后的开始日期(除非它是一个"空"句点,两个日期都设置为默认值(DateTime) - 是的,这不是一个很棒的设计,但你得到的是我意味着...)尝试先设置开始日期将引发异常.

当我们使用对象初始化器时,它会变得更加严重.由于C#不保证任何赋值顺序,我们不能做出任何安全假设,代码可能会也可能不会抛出异常,具体取决于编译器的选择.坏!

这最终是类的设计问题.由于该属性无法"知道"您正在更新这两个值,因此在实际更改这两个值之前,它无法"关闭"验证.您应该将两个属性设置为只读并提供同时设置两个属性的方法(丢失对象初始化程序的功能)或从属性中完全删除验证代码(可能引入另一个只读属性,如IsValid,或验证它需要的时候).

ORM"保湿"*

在简单化的视图中,水合意味着将持久化数据恢复到对象中.对我来说,这是添加属性设置器后面的逻辑的最大问题.

许多/大多数ORM将持久值映射到属性中.如果您具有更改属性设置器内的对象状态(其他成员)的验证逻辑或逻辑,您将最终尝试验证"不完整"对象(一个仍在加载).这与对象初始化问题非常相似,因为您无法控制字段"水合"的顺序.

大多数ORM允许您将持久性映射到私有字段而不是属性,这将允许对象被水合,但如果您的验证逻辑主要位于属性设置器中,您可能必须在其他位置复制它以检查加载的对象是否有效或不.

由于许多ORM工具通过使用映射到字段的虚拟属性(或方法)来支持延迟加载(ORM的一个基本方面!),因此ORM无法将延迟加载对象映射到字段中.

结论

因此,最后,为了避免代码重复,允许ORM尽可能地执行,根据字段设置的顺序防止出现意外异常,将逻辑从属性设置器移开是明智的.

我还在弄清楚这个'验证'逻辑应该在哪里.我们在哪里验证对象的不变量方面?我们在哪里进行更高级别的验证?我们是否在ORM上使用钩子来执行验证(OnSave,OnDelete,...)?等等等.但这不是这个答案的范围.


Rog*_*son 10

Setter不携带任何语义信息.

例如

blogpost.PublishDate = DateTime.Now;
Run Code Online (Sandbox Code Playgroud)

这是否意味着帖子已发布?或者只是已经设定了发布日期?

考虑:

blogpost.Publish();
Run Code Online (Sandbox Code Playgroud)

这清楚地表明了应该发布博客帖子的意图.

此外,setter可能会破坏对象不变量.例如,如果我们有一个"三角形"实体,则不变量应该是所有角度的总和应该是180度.

Assert.AreEqual (t.A + t.B + t.C ,180);
Run Code Online (Sandbox Code Playgroud)

现在,如果我们有setter,我们可以轻松打破不变量:

t.A = 0;
t.B = 0;
t.C = 0;
Run Code Online (Sandbox Code Playgroud)

所以我们有一个三角形,其中所有角度的总和为0 ......这真的是一个三角形吗?我会说不.

用方法替换setter可能会迫使我们维护不变量:

t.SetAngles(0,0,0); //should fail 
Run Code Online (Sandbox Code Playgroud)

此类调用应抛出一个异常,告诉您这会导致您的实体出现无效状态.

因此,您可以使用方法而不是setter来获取语义和不变量.


Mat*_*son 7

这背后的原因是实体本身应该负责改变其状态.没有任何理由您需要在实体本身之外的任何地方设置属性.

一个简单的例子是具有名称的实体.如果您有公共设置器,则可以从应用程序的任何位置更改实体的名称.如果您改为移除该setter并将类似方法放入ChangeName(string name)您的实体,这将是更改名称的唯一方法.这样,您可以添加任何类型的逻辑,这些逻辑在您更改名称时将始终运行,因为只有一种方法可以更改它.这比将名称设置为某些东西要清楚得多.

基本上这意味着您在私下保持状态时公开公开您的实体的行为.