我正在尝试为我的应用程序应用域驱动设计原则,其中包含包含数据字段和业务逻辑的丰富域模型。我读过许多DDD书籍,但似乎他们的域名模型(称为实体)非常简单。当我有一个包含10-15个数据字段的域模型时会出现问题,例如下面的那个:
class Job extends DomainModel{
protected int id;
protected User employer;
protected string position;
protected string industry;
protected string requirements;
protected string responsibilities;
protected string benefits;
protected int vacancy;
protected Money salary;
protected DateTime datePosted;
protected DateTime dateStarting;
protected Interval duration;
protected String status;
protected float rating;
//business logic below
}
如您所见,此域模型包含许多数据字段,所有这些字段都很重要,无法将其删除。我知道一个好的富域模型不应该包含setter方法,而是将其数据传递给构造函数,并使用业务逻辑来改变状态。但是,对于上面的域模型,我无法将所有内容传递给构造函数,因为它将导致构造函数方法中的15个以上参数。一个方法不应该包含超过6-7个参数,你不觉得吗?
那么我该怎么做才能处理具有大量数据字段的域模型?我应该尝试分解吗?如果是这样,怎么样?或者,我应该在实例化时使用Builder类或反射来初始化它的属性,这样我就不会用如此多的参数污染构造函数?谁能提出一些建议?谢谢。
答案 0 :(得分:9)
您错过的是价值对象的概念。值对象是小的,不可变的对象,在相应的域中具有意义。
我不知道您所在域的具体细节,但查看您的Job
实体,可能会有一个如下所示的值对象JobDescription
:
class JobDescription {
public JobDescription(string position, string requirements, string responsibilities) {
Position = position;
Requirements = requirements;
Responsibilities = responsibilities;
}
public string Position {get;}
public string Requirements {get;}
public string Responsibilities {get;}
}
这是C#代码,但我认为无论您使用何种语言,这个想法都应该清晰。
基本思路是以相应领域中有意义的方式对值进行分组。这当然意味着值对象也可以包含其他值对象。
您还应确保通过值而不是引用来比较值对象,例如:通过在C#中实现IEquatable<T>
。
如果使用这种方法重构代码,您将在实体上获得更少的字段,因此使用构造函数注入(强烈建议)再次变得可行。
有关与示例代码无直接关联的示例代码的进一步说明:
域模型是一个整体,实体是其中的一部分。因此,您的基类应该被称为Entity
而不是DomainModel
。
您应该创建班级private
的字段,并提供protected
个访问者,以保持封装。
答案 1 :(得分:2)
在你的Job域模型对象中发生了很多事情 - 它似乎混合了大量的问题,并且(至少对我来说)暗示了一些有限的背景,其中一些很容易辨别出来做一个例子。
当您考虑与“工作”模型相互作用的事物时,是否需要检查或改变工资属性和工业属性?例如?
在不知道域名的完整细微差别的情况下,您获得职位所获得的薪酬和您所从事的行业并没有真正联系,是吗?不是一个修辞点;这些是您需要向领域专家提出的问题。
如果他们没有任何互动,那么你已经发现这两件事存在于两个不同的有界背景中。薪酬方面不需要与行业方面进行任何互动,反之亦然,即使他们这样做了,他们是否需要同时在同一过程中保持状态?
考虑一个人如何成为一名员工的生命周期;一个人申请工作。这份工作有规格,工资范围。这个人参加面试。租用者向该人提供该职位。该人接受。这个人现在是一名雇员,而不再是候选人。新员工现在正在积累假期和福利,并有开始日期等。
DDD教导我们,对世界的单一,统一的观点很少能正确地解决任何问题。请探索有界的背景 - 因此,您的软件将更加灵活和灵活。