从贫血领域到领域驱动

Luc*_*ian 3 c# design-patterns entity-framework anemic-domain-model

我试图找到一个清晰而简单的例子,说明贫血领域的真正含义.有很多理论,还有很多很好的答案.尽管如此,我还是无法清楚地了解"贫血领域"的含义到底有多大.因此,我相信看到一个贫血领域设计的虚拟实际例子会更简单,而不是问你怎么可能演变成一个域驱动的...

所以,假设我们有一个TaskData类型的数据实体:

public class TaskData
{
    public Guid InternalId { get; set; }

    public string Title { get; set; }

    public string Details { get; set; }

    public TaskState ExplicitState { get; set; }

    public IEnumerable<TaskData> InnerTasks { get; set; }

}
Run Code Online (Sandbox Code Playgroud)

并且需要一个称为" ActualState " 的附加属性,它是一个计算状态:如果Task有内部子任务,则该值严格依赖于子节点,否则," ActualState "等于" ExplicitState "

如果我将这个逻辑写在一个单独的服务类中(我称之为" 引擎 "),我们有:

internal class TaskStateCalculator
{
    public TaskState GetState(TaskData taskData)
    {
        if (taskData.InnerTasks.Any())
        {
            if (taskData.InnerTasks.All(x => this.GetState(x) == TaskState.Done))
            {
                return TaskState.Done;
            }
            if (taskData.InnerTasks.Any(x => this.GetState(x) == TaskState.InProgress))
            {
                return TaskState.InProgress;
            }

            return TaskState.Default;
        }

        return taskData.ExplicitState;
    }       
}
Run Code Online (Sandbox Code Playgroud)

一个问题是:

上面的代码是否反映了一个贫血的域设计,即使TaskStateCalculator服务/引擎是我的域层的一部分?如果是,为了避免它,我们需要在TaskData类中移动逻辑(并将TaskData重命名为Task).我对吗?

第二个问题是(实际上它们的链):

如果我们遇到更困难的情况怎么办?假设在Task实体中需要一个名为ComputeSomething的属性,并且该属性的逻辑需要访问整个Task的存储库.在这种情况下,Task类将依赖于TaskRepository.这样可以吗?EF将如何构建此类的实例?有什么选择?

jga*_*fin 6

我试图找到一个清晰而简单的例子,说明贫血领域的真正含义

事实上,从贫血领域模式到富裕领域模型真的很容易.

  1. 将所有属性设置器设置为private,然后添加方法(如果要更改模型的状态).
  2. 评估所有Demeter违规并在适当的地方添加方法.

最终你会有一个正确的模型.

在你的情况下,我将封装那个逻辑,TaskData因为你的TaskStateCalculator违反了Demeter法则

public class TaskData
{
    public Guid InternalId { get; private set; }

    public string Title { get; private set; }

    public string Details { get; private set; }

    public TaskState ExplicitState { get; private set; }

    public IEnumerable<TaskData> InnerTasks { get; private set; }

    public TaskState GetState()
    {
        if (!InnerTasks.Any())
            return ExplicitState;

        if (InnerTasks.All(x => this.GetState(x) == TaskState.Done))
        {
            return TaskState.Done;
        }

        if (InnerTasks.Any(x => this.GetState(x) == TaskState.InProgress))
        {
            return TaskState.InProgress;
        }

        return TaskState.Default;
    }       
}
Run Code Online (Sandbox Code Playgroud)

另一件事是我可能根本不会将InnerTasks集合暴露给外部世界(只是将它作为成员字段).但很难说,因为我不知道在其他场景中如何使用该类.

为什么私人制定者

每次你必须改变多个属性时,用方法描述行为通常会更好,因为这样就不可能忘记改变所有必需的属性.方法还比更改一组属性更好地描述了您要做的事情.

即使您只更改了一个属性,该属性也可以将类设置为无效状态,因为更改可能与类中的其余信息不兼容.不要忘记封装是OOP的核心原则之一