模型重构

架构元模型定义了模型中使用的概念和使用规则。 —— 《架构师修炼之道》

你可以将其对比于领域模型

聚合行为

对于领域模型来说,我们也无法直接在代码中实现架构元模型的所有概念。但是,我们所要做的事不断减少模型与代码之间的差异。如果我们不创建模式,而直接开始编写代码,那么我们会收获一堆上帝类。但是,反过来,当我们有一堆上帝类的时候,那么我们就需要从类中把行为都抽取出来。

当我们的贫血模型,拥有了行为,就可以进一步构成富血模型,符合面向对象(OO)的思想。进一步的,我们可以从业务的角度来考虑这个问题,将充血模型改为领域模型。

由内到外剥离,由外到内聚合

对于那些已经采用 DDD 架构的项目来说,往往会遇到一些领域模型不完整、包含非领域相关代码等的情况。

遇到这种情况时,可以尝试:

  1. 由内到外剥离非模型相关代码。只需要浏览一遍领域模型相关的代码,然后剥离不属于模型的代码;通过依赖注入、工厂模式等方式,建立整洁的 domain 层。
  2. 由外到内聚合领域模型相关逻辑。这是一个复杂的过程,需要每个使用到模型的调用方,再看是属于领域相关的行为。

第一步可以在短期内快速实现,而第二步则需要一个漫长的过长 —— 取决于项目的大小。

识别模式 1:输入参数

你懂的

识别模式 2:返回参数

你懂的

优化创建

笔者在某个重构项目中,遇到模型的创建逻辑很复杂 —— 参数多、场景多,所以做的第一件事情是:使用工厂模式优化了创建过程。

参考工厂模式。

重命名:统一语言

在 DDD 中强调了统一语言的重要性,为此我们有必要对代码中的模型名称及其行为进行检视。在软件工程实践不好的团队中,你往往会出现对于同一个事件,往往会有多种命名方式 。哪怕你觉得它是不正确的,因为 ownership 的缺乏,也没有人来统一对应的命名。

所以,在我们决定继续往下走之前,先学习一下怎么命名。

计算机科学只存在两个难题:缓存失效和命名。 —— Phil KarIton

Arlo Belshee 命名的七步骤原文链接:naming is a process

但是还是更习惯于原来的文章中的:

阶段 解释 示例
空白 没有名称 doSomething()
凑合 名称不能准确反应元素的含义 preload()
沾边 名称至少反映了元素某一方面的功能 DomSomethingEvilToDB()
反映功能 名称直接描述了元素的所有功能 ParseXmlAndStoreFightToDbAndLocalCacheAndStartProcessing()
反映角色 名称充分地反映了元素在架构中的角色 StoreFightlightToDatabaseAndStartProcessing
反映意图 名称不仅反映元素的功能,还能反映其目的。 BeginTrackingFlight()
领域抽象 名称超越了单个元素本身,成为一个新的抽象概念。 MonitoringPanle.Add(new Flight())

偶然间,我看到我找到我书架上的《重构与模式》时,刚好看到一本《实现模式》,顺便看了看,发现书的内容对于本文有启发意义。

《实现模式》概览

书中提及了四五种类型类、状态、消息与流(原行为)、方法,但是对于我们的统一语言工作来说,只需要重命名类、方法、状态就够了。

对应的解释如下:

数据的变化比逻辑要繁琐得多,正是这种现象让类有了存在的意义。—— 《实现模式》

对于继承的类来说,它应该遵循这么一些原则:

  • 超类名称要简单
  • 子类名称要合格

状态

状态包含了变量、字段、常量、局部参数、参数、参数对象等等。

方法

在命名类和操作时要描述它们的效果和目的,而不要表露它们是通过何种方式达到目。 —— Eric Evans

容器

离心分离模型:消除二义性

接下来就是处理剩下的 bean、model 等等模型

在一个系统中,你会存在这么一些不同的 model:

(PS:部分描述可能不准确,欢迎指正)

  • 与数据库表结构对应的 DO( Data Object)/ PO(Persistant Object)。
  • 查询数据的 Query、Request。
  • 对外传输的对象:DTO( Data Transfer Object)。
  • 业务层之间的数据对象:VO(Value Object) / BO(Business Object)。
  • 访问数据库的:DAO (Data Access Object数据访问对象)。
  • 以及我们想要的 DDD 中的实体 Entity
  • 还有其它的 POJO( Plain Ordinary Java Object)

但是它们都是 model,所以它们都被扔到 model 中……,又或者是 bean 中……。导致,你有了一个巨大比的 model 层。

所以,在 DDD 又或者是 Clean Architecture,我们重新命名了不同的模式:

  • 使用 Command / Request 作为输入参数。其中的 Command 模式在完成后需要发出对应的 Event。
  • 使用 Response / DTO / Representation 作为返回结果。
  • 对 Entity 大家保持了一致的意见
  • 还有 PO / DO 作为作为数据库的存储模型
  • DAO 作为数据库的访问模型
  • ……

不过,其实你只要不再让使用 model 和 bean,相信会有更多地收获。

提取参数对象

如果一个类包含大量的参数,并且参数中存在一些相似的情形。对于概念统一的情况,可以提取成参数对象。

处理过程逻辑

过程不应该模型的一部分,但是它是领域的一部分。

如 Eric Evans 在所说,区分是否显式表达概念的关键在于:过程是否经常被领域专家谈起,又或者只是计算机程序机制的一部分。

这时候,我们就需要规格(Specification)模式。

下一节:模式是某种场合下对某个问题的一个解决方案的一种结构化展现。 —— Jon Vlissides(GoF 成员)《设计模式沉思录》