Skip to content

实现领域对象

领域对象,即是领域模型里的对象,是领域模型的组成部分,在实现的时候,它是领域模块里的用程序代码表示的对象。

举个例子来说,问题域是需要一个工具可以快速的在路上移动。领域模型是一辆车,通过一些设计图纸等体现出来。这个抽象的模型里,会有轮子这个领域对象,是其中的组成部分。然后用代码,实现了整个领域模型,变成实际的代码,那么轮子这个领域对象就对应到程序中有一个轮子的类。

如何使用领域对象?

在领域模块内部,为了最好的表达如何解决领域问题,并不需要对使用领域对象做太多限制。

在领域模块外部,要使用领域对象,主要以下几种情况

  • 实现领域模块里的接口、抽象类等,给领域对象当帮手
  • 给领域模块发送领域命令
  • 监听领域事件
  • 复用一些无副作用的领域对象,比如值对象,某个特定算法的领域服务,单纯查询功能

领域对象就是实体?

不是。领域模型中包含的所有的对象都是领域对象,事件、命令、聚合、服务等等,只要建模出一个对象,那它就是领域对象,最终要在领域模块中有对应的代码体现出来。

建模的时候要把所有的领域对象都建出来吗?

不需要。建模的时候,要抓重点,如果要求建好所有领域对象,那成本会增大很多,收益很低。比如,每次建模出一个聚合,一般都隐含聚合会有一个工厂对象和一个仓储对象,我们没有必要去为每个聚合都写一遍,那显得太过于啰嗦了。再比如,一些值对象,用来封装一些数值,建模的时候,由于业务上不太关心其内部细节,我们只是提了一下有这么个数据,并不会细化里面每一个字段。还有一些通用的对象,比如地址、邮件、姓名、日期等等,我们也没有必要在建模的时候,特意都列出来,否则显得过于啰嗦。

采用面向对象的领域模型,必须采用面向对象的程序语言?

不一定。

实际上面向对象更多是一种思想,即使不是面向对象的语言,也可以实现面向对象的效果。只要能够实现封装一些功能和支持功能的数据,就可以了。比如,C 语言中,可以用结构体及围绕结构体的一组函数实现一个对象的效果,通过头文件实现隐藏某些函数。

大部分情况下,采用支持面向对象编程的语言都是最好的方式,省去了很多的麻烦,提升开发效率,让对象的定义显式化。

领域对象的实现代码必须写到领域模块里吗?

不需要。我们要达到的效果,是在静态的代码上区分开领域模块和非领域模块,也就是领域逻辑的代码,写到领域模块里,非领域逻辑的代码,写到领域模块之外。因此,可以这么做

  • 在领域模块中定义一个接口,在领域模块外去实现
    • 接口表达领域概念
    • 接口的实现是具体技术
  • 在领域模块中定义一个抽象类,在领域模块外去实现
    • 抽象类表达领域概念
    • 抽象类里具体函数里是领域逻辑
    • 抽象类的抽象函数里是技术实现,交给外部模块去实现

在运行期领域对象可以依赖外部的对象?

是的。在运行期,如果领域模块外的对象,比如数据库访问对象出故障了,而某个领域对象的具体实现类里用到了这个数据库访问对象,那这个领域对象也会出故障。这是符合期望的,领域对象不可能脱离实际技术去运行的。

我们更关心的是在编译期,把领域的和非领域的代码分开,而不是运行期。运行的时候,程序实际上是在领域模块里的代码和外部实现代码之间不断地跳跃。

如何区分领域逻辑和非领域逻辑?

考虑导致一段代码发生变更的原因是来自哪里,来自领域逻辑变更的,来自领域模型变更的,那就是领域逻辑代码;否则就不是领域逻辑代码。

比如,有一段代码要决定保存后是否发送,之所以有这段代码,是因为用户界面上有一个保存按钮,和一个保存并发送按钮。假如这个用户界面发生改版,去掉了保存并发送按钮,这段代码就不需要了。可见能够导致这段代码变更的原因,是来自于 UI,那这段代码,就不是领域逻辑代码。再比如,一段代码拼接 sql,假如以后我们更换了数据库,或者更换了访问数据的框架,那么这段代码也会要跟着变更,引起这段代码变更的原因,来自于数据库访问的技术,那这段代码也不是领域逻辑代码。再比如,一段代码,表达下单金额必须大于最小起订价才能下单,只有在业务上这个规则发生变更,才会改动这个代码,这个代码就是领域逻辑代码。