代码之家  ›  专栏  ›  技术社区  ›  cbp

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

  •  73
  • cbp  · 技术社区  · 15 年前

    我试图理解贫血域模型以及为什么它们被认为是一种反模式。

    这是一个真实的例子。

    我有一个员工类,它有很多属性——姓名、性别、用户名等等。

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

    接下来,我们有一个系统,它包括在销售人员中均匀地轮流接听来电和查询网站(称为“销售线索”)。这个系统相当复杂,因为它涉及到全面的问题,检查假期,员工偏好等。因此,这个系统目前被分为一个服务:员工阅读轮换服务。

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

    我在这种方法中并没有遇到很多问题,但据推测,这是我应该大喊大叫的地方,因为它是 贫血领域模型 .

    但对我来说,还不清楚LeadRotation服务的逻辑应该在哪里。它应该领先吗?它应该放在员工身上吗?

    那么轮班服务所需要的所有注入存储库呢——如果在处理员工的大部分时间里,我们不需要这些存储库中的任何一个,那么如何将它们注入员工呢?

    4 回复  |  直到 9 年前
        1
  •  53
  •   radman    15 年前

    在这种情况下,这不构成贫血域模型。贫血域模型是 specifically about validating and transforming the objects . 因此,这方面的一个例子是,如果外部函数实际更改了员工的状态或更新了他们的详细信息。

    在这种情况下,发生的事情是,你要带走所有员工,并根据他们的信息选择其中一个。有一个独立的对象来检查其他人,并就所发现的东西做出决定,这是很好的。有一个对象用于将对象从一种状态转换到另一种状态是不正常的。

    在您的案例中,贫血域模型的一个例子是有一个外部方法

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

    这需要一个Employee对象,并更新它在一周内的工作时间,确保在工作时间超过某个限制时提升标志。问题在于,如果您只有Employee对象,那么您就不知道如何在正确的限制条件下修改他们的工作时间。在这种情况下,处理它的方法是将updateHours方法移动到Employee类中。这就是贫血域模型反模式的关键所在。

        2
  •  29
  •   mdma    15 年前

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

    根据经验法则,与域对象内在关联并完全按照该域对象实例定义的行为可以包括在域对象中。否则,为了明确职责,最好将其放在外部的合作者/服务中,就像您所做的那样。

        3
  •  14
  •   ima    15 年前

    这一切都在你的头脑中-考虑将轮换服务作为领域模型的一部分,问题就解决了。

    轮换需要保留许多员工的信息,因此它既不属于Lead,也不属于任何单个员工对象。它本身应该是一个域对象。

    只需将“RotationService”重命名为“Organization.UserSupportDepartment”就很明显了。

        4
  •  0
  •   Community CDub    8 年前

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

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

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

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

    急性呼吸窘迫综合征 模型 不是一个 对象 . 我在另一个答案中谈到了他们之间的区别… https://stackoverflow.com/a/31780937/116442

    从你的问题来看,你打破了我的前两个领域分析建模规则:

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

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

    EnquiryHandlerModel

    推荐文章