🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### Separate Domain from Presentation(将领域和表述/显示分离) (译注:本节保留domain-,presentation-logic,UI,class,object等英文词) 某些GUI class 之中包含了domain logic(领域逻辑)。 将domain logic(领域逻辑)分离出来,为它们建立独立的domain class。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e8a5946.gif) **动机(Motivation)** 提到面向对象,就不能不提model-view-controller (MVC,模型-视图-控制器) 模式。在Smalltalk-80环境中,人们以此模式维护GUI(图形用户界面)和domain object (领域对象)间的关系。 MVC模式的最核心价值在于:它将用户界面代码(即所谓view,视图;亦即现今常说的presentation,表述)和领域逻辑(即所谓model,模型)分离了。presentation class 只含用以处理用户界面的逻辑;domain class不含任何与程序外观相关的代码,只含业务逻辑(business logic)相关代码。将程序中这两块复杂的部分加以分离,程序未来的修改将变得更加容易,同时也使同一业务逻辑(business logic)的多种表述(显示)方式成为可能。那些熟稔面向对象技术的程序员会毫不犹豫地在他们的程序中进行这种分离,并且这种作法也的确证实了它自身的价值。 但是,大多数人并没有在设计中采用这种方式来处理GUI。大多数带有client-server GUIs 的环境都釆用双层(two-tier)逻辑设计:数据保存在数据库中,业务逻辑(business logic)放在presentation class 中。这样的环境往往迫使你也倾向这种风格的设计,使你很难把业务逻辑放在其他地方。 Java 是一个真正意义上的面向对象环境,因此你可以创建内含业务逻辑的非视觉性领域对象(nonvisual domain objects )。但你却还是会经常遇到上述双层风格写就的程序。 **作法(Mechanics)** - 为每个窗口(window)建立一个domain class。 - 如果窗口内有一张表格(grid),新建一个class 来表示其中的行(row),再以窗口所对应之domain class 中的一个群集(collection)来容纳所有的row domain objects。 - 检查窗口中的数据。如果数据只被用于UI,就把它留着;如果数据被domain logic使用,而且不显示于窗口上,我们就以Mocve Field 将它搬移到domain class 中;如果数据同时被UI 和domain logic 使用,就对它实施Duplicate Observed Data,使它同时存在于两处,并保持两处之间的同步。 - 检査presentation class 中的逻辑。实施 Extract Method 将presentation logic 从domain logic 中分开。一旦隔离了domain logic。再运用 Move Method 将它移到domain class。 - 以上步骤完成后,你就拥有了两组彼此分离的classes:presentation classes 用以处理GUI,domain logic 内含所有业务逻辑(business logic)。此时的domain classes 组织可能还不够严谨,更进一步的重构将解决这些问题。 **范例:(Example)** 下面是一个商品订购程序。其GUI 如图12.7所示,其presentation class 与图12.8 所示的关系式数据库(relational database)互动。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e8bab16.gif) 图12.7 启动程序的用户界面 ![](https://box.kancloud.cn/2016-08-15_57b1b5e8cefae.gif) 图12.8 订单程序所用的数据库 - 所有classes 都是《SQL table》,粗体字表示主键(primary key),《FK》表示外键(foreign keys) 所有行为(包括GUI 和定单处理)都由OrderWindow class 处理。 首先建立一个Order class 表示「定单」。然后把Order 和OrderWindow 联系起来, 如图12.9。由于窗口中有一个用以显示定单的表格(grid),所以我们还得建立一个OrderLine,用以表示表格中的每一行(rows)。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e8e5116.gif) 图12.9 OrderWindow class 和Order class 我们将从窗口这边而不是从数据库那边开始重构。当然,一开始就把domain model 建立在数据库基础上,也是一种合理策略,但我们最大的风险源于presentation logic 和domain logic之间的混淆,因此我们首先基于窗口将这些分离出来,然后再考虑对其他地方进行重构。 面对这一类程序,在窗口中寻找内嵌的SQL (结构化查询语言)语句,会对你有所帮助,因为SQL 语句获取的数据一定是domain data 。 最容易处理的domain data 就是那些不直接显示于GUI 者。本例数据库的Customer table 中有一个Codes 值域,它并不直接显示于GUI,而是被转换为一个更容易被人理解的短语之后再显示。程序中以简单型别(而非AWT组件)如String 保存这个值域值。我们可以安全地使用Move Field 将这个值域移到domain class。 对于其他值域,我们就没有这么幸运了,因为它们内含AWT 组件,既显示于窗口, 也被domain object 使用。面对这些值域,我们需要使用Duplicate Observed Data,把一个domain field 放进Order class,又把一个相应的AWT field 放进OrderWindow class。 这是一个缓慢的过程,但最终我们还是可以把所有domain logic field 都搬到domain class。进行这一步骤时,你可以试着把所有SQL calls 都移到domain class,这样你就是同时移动了database logic 和domain data。最后,你可以在OrderWindow 中移除import java.sql 之类的语句,这就表示我们的重构告一段落了。在此阶段中 你可能需要大量运用 Extract Method 和 Move Method。 现在,我们拥有的三个classes,如图12.10所示,它们离「组织良好」还有很大的距离。不过这个模型的确已经很好地分离了presentation logic 和domain logic (business logic)。本项重构的进行过程中,你必须时刻留心你的风险来自何方。 如果「presentation logic 和domain logic 混淆」是最大风险,那么就先把它们完全分开,然后才做其他工作;如果其他方面的事情(例如产品定价策略〉更重要,那么就先把那一部分的logic 从窗口提炼出来,并围绕着这个高风险部分进行重构,为它建立合适的结构。反正domain logic 早晚都必须从窗口移出,如果你在处理高风险部分的重构时会遗留某些logic 于窗口之中,没关系,就放手去做吧。 ![](https://box.kancloud.cn/2016-08-15_57b1b5e902e37.gif) 图12.10 将数据安置(分散)于domain classes 中