避免贫血领域模型-一个真实的例子


76

我试图了解Anemic域模型以及为什么它们被认为是反模式。

这是一个真实的例子。

我有一个Employee类,它具有大量属性-名称,性别,用户名等

public class Employee
{
    public string Name { get; set; }
    public string Gender { get; set; }
    public string Username { get; set; }
    // Etc.. mostly getters and setters
}

接下来,我们有一个系统,该系统涉及在销售人员之间平均轮流传入电话和网站查询(称为“潜在客户”)。该系统非常复杂,因为它涉及轮循查询,检查假期,员工喜好等。因此,该系统目前被分离为一项服务:EmployeeLeadRotationService。

public class EmployeeLeadRotationService : IEmployeeLeadRotationService
{
     private IEmployeeRepository _employeeRepository;
     // ...plus lots of other injected repositories and services

     public void SelectEmployee(ILead lead)
     {
         // Etc. lots of complex logic
     }
}

然后,在我们的网站查询表的背面,我们有如下代码:

public void SubmitForm()
{
    var lead = CreateLeadFromFormInput();

    var selectedEmployee = Kernel.Get<IEmployeeLeadRotationService>()
                                 .SelectEmployee(lead);

    Response.Write(employee.Name + " will handle your enquiry. Thanks.");
}

使用这种方法我并没有遇到很多问题,但是应该说这是我应该大呼过瘾的东西,因为它是Anemic Domain Model

但对我来说,不清楚领导轮换服务中的逻辑应该去哪里。它应该领先吗?应该在员工身上吗?

轮换服务所需的所有注入存储库等如何-考虑到大多数时候与员工打交道,我们不需要这些存储库,它们将如何注入到员工中呢?


那么ILead,如果不明显将.SelectEmployee()放入其中,会是什么样子呢?
西蒙·布坎

好吧,这种情况下的线索是网络查询,因此它将具有Comment属性等。但是我们也有电话查询,应用程序,报价等,它们都稍有不同。ILead接口将具有LocationOfLead,TimeOfLead等属性
cbp 2010年

1
因此,我认为将.SelectEmployee()放在首位比雇员更明显,但这并不能解决其他问题:依赖存储库的注入;缺乏SoC;在很多时候(例如,在报告过程中),将所有SelectEmployee代码都包含在Lead类中(实际上,我们将需要一个LeadBase类,这样我们就不会在所有继承的Lead类中重用该代码)的总体复杂性真的不关心如何选择员工。
cbp

Answers:


55

在这种情况下,这并不构成贫血域模型。Anemic域模型 专门用于验证和转换对象。因此,这方面的一个示例是外部函数实际上是否更改了Employees的状态或更新了其详细信息。

在这种情况下,您要带走所有员工,并根据他们的信息选择其中一个。最好有一个单独的对象来检查其他对象并根据发现的内容做出决定。具有用于将对象从一种状态转换为另一种状态的对象是不可行的。

在您的情况下,贫血域模型的一个示例是使用外部方法

updateHours(Employee emp) // updates the working hours for the employee

它接受一个Employee对象并更新其一周的工作时间,并确保如果工作时间超过特定限制,则引发标志。问题是,如果只有Employee对象,那么您将不知道如何在正确的约束范围内修改其工作时间。在这种情况下,处理该问题的方法是将updateHours方法移到Employee类中。这是Anemic Domain Model反模式的关键。


但是,如果Employee是数据库的持久对象,该怎么办?为什么要在其中放置方法?对于不将方法放入内部的DTO,同样的问题同样有效。那您将把updateHours方法放在哪里?
帕斯卡

updateHours属于Employee类。您应该将更新小时所需的任何数据(例如已完成的任务)传递给它。协作者对象也很好,但是最好没有服务。
MauganRa

30

我认为您的设计很好。如您所知,贫血领域模型反模式是对避免在领域对象中编码任何行为的趋势的强烈反对。但是相反,这并不意味着与该域对象有关的所有行为必须由该对象封装。

根据经验,可以将本质上与域对象相关联并且完全根据一个域对象实例定义的行为包括在域对象中。否则,为了使职责明确,最好像您一样将其放在外部的协作者/服务中。


5
究竟。这是一个带有许多内部逻辑的外部模块(LeadQueueManager或其他),这绝对不是贫乏的领域模型。员工对呼叫队列调度了解多少?没什么;)
TomTom 2010年

14

这一切都在您的脑海中-将轮换服务视为域模型的一部分,问题就可以解决。

轮换需要保留有关许多员工的信息,因此它既不属于领导,也不属于任何单个员工对象。它确实应该成为域对象。

只需将“ RotationService”重命名为“ Organization.UserSupportDepartment”之类即可。


0

如果您的域模型仅包含角色和事物,而不包含作为行为的活动,那么它是贫乏的。但是,我说的是关于模型而不是对象的行为。我在另一个答案中谈到了它们之间的区别... https://stackoverflow.com/a/31780937/116442

从您的问题出发,您打破了我的前两个领域分析建模规则:

  1. 建模为(记录的)活动的行为是域模型的核心。首先添加它们。
  2. 将域活动建模为类,而不是方法。

我将向模型添加活动“查询”。有了它,模型便具有了行为,并且可以在没有外部控制器或脚本的情况下组合并作为一组对象工作。

查询处理程序模型

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.