### 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 中
- 译序 by 侯捷
- 译序 by 熊节
- 序言
- 前言
- 章节一 重构,第一个案例
- 起点
- 重构的第一步
- 分解并重组statement()
- 运用多态(Polymorphism)取代与价格相关的条件逻辑
- 结语
- 章节二 重构原则
- 何谓重构
- 为何重构
- 「重构」助你找到臭虫(bugs)
- 何时重构
- 怎么对经理说?
- 重构的难题
- 重构与设计
- 重构与性能(Performance)
- 重构起源何处?
- 章节三 代码的坏味道
- Duplicated Code(重复的代码)
- Long Method(过长函数)
- Large Class(过大类)
- Long Parameter List(过长参数列)
- Divergent Change(发散式变化)
- Shotgun Surgery(散弹式修改)
- Feature Envy(依恋情结)
- Data Clumps(数据泥团)
- Primitive Obsession(基本型别偏执)
- Switch Statements(switch惊悚现身)
- Parallel Inheritance Hierarchies(平行继承体系)
- Lazy Class(冗赘类)
- Speculative Generality(夸夸其谈未来性)
- Temporary Field(令人迷惑的暂时值域)
- Message Chains(过度耦合的消息链)
- Middle Man(中间转手人)
- Inappropriate Intimacy(狎昵关系)
- Alternative Classes with Different Interfaces(异曲同工的类)
- Incomplete Library Class(不完美的程序库类)
- Data Class(纯稚的数据类)
- Refused Bequest(被拒绝的遗贈)
- Comments(过多的注释)
- 章节四 构筑测试体系
- 自我测试代码的价值
- JUnit测试框架
- 添加更多测试
- 章节五 重构名录
- 重构的记录格式
- 寻找引用点
- 这些重构准则有多成熟
- 章节六 重新组织你的函数
- Extract Method(提炼函数)
- Inline Method(将函数内联化)
- Inline Temp(将临时变量内联化)
- Replace Temp with Query(以查询取代临时变量)
- Introduce Explaining Variable(引入解释性变量)
- Split Temporary Variable(剖解临时变量)
- Remove Assignments to Parameters(移除对参数的赋值动作)
- Replace Method with Method Object(以函数对象取代函数)
- Substitute Algorithm(替换你的算法)
- 章节七 在对象之间搬移特性
- Move Method(搬移函数)
- Move Field(搬移值域)
- Extract Class(提炼类)
- Inline Class(将类内联化)
- Hide Delegate(隐藏「委托关系」)
- Remove Middle Man(移除中间人)
- Introduce Foreign Method(引入外加函数)
- Introduce Local Extension(引入本地扩展)
- 章节八 重新组织数据
- Self Encapsulate Field(自封装值域)
- Replace Data Value with Object(以对象取代数据值)
- Change Value to Reference(将实值对象改为引用对象)
- Replace Array with Object(以对象取代数组)
- Replace Array with Object(以对象取代数组)
- Duplicate Observed Data(复制「被监视数据」)
- Change Unidirectional Association to Bidirectional(将单向关联改为双向)
- Change Bidirectional Association to Unidirectional(将双向关联改为单向)
- Replace Magic Number with Symbolic Constant(以符号常量/字面常量取代魔法数)
- Encapsulate Field(封装值域)
- Encapsulate Collection(封装群集)
- Replace Record with Data Class(以数据类取代记录)
- Replace Type Code with Class(以类取代型别码)
- Replace Type Code with Subclasses(以子类取代型别码)
- Replace Type Code with State/Strategy(以State/strategy 取代型别码)
- Replace Subclass with Fields(以值域取代子类)
- 章节九 简化条件表达式
- Decompose Conditional(分解条件式)
- Consolidate Conditional Expression(合并条件式)
- Consolidate Duplicate Conditional Fragments(合并重复的条件片段)
- Remove Control Flag(移除控制标记)
- Replace Nested Conditional with Guard Clauses(以卫语句取代嵌套条件式)
- Replace Conditional with Polymorphism(以多态取代条件式)
- Introduce Null Object(引入Null 对象)
- Introduce Assertion(引入断言)
- 章节十一 处理概括关系
- Pull Up Field(值域上移)
- Pull Up Method(函数上移)
- Pull Up Constructor Body(构造函数本体上移)
- Push Down Method(函数下移)
- Push Down Field(值域下移)
- Extract Subclass(提炼子类)
- Extract Superclass(提炼超类)
- Extract Interface(提炼接口)
- Collapse Hierarchy(折叠继承关系)
- Form Template Method(塑造模板函数)
- Replace Inheritance with Delegation(以委托取代继承)
- Replace Delegation with Inheritance(以继承取代委托)
- 章节十二 大型重构
- 这场游戏的本质
- Tease Apart Inheritance(梳理并分解继承体系)
- Convert Procedural Design to Objects(将过程化设计转化为对象设计)
- Separate Domain from Presentation(将领域和表述/显示分离)
- Extract Hierarchy(提炼继承体系)
- 章节十三 重构,复用与现实
- 现实的检验
- 为什么开发者不愿意重构他们的程序?
- 现实的检验(再论)
- 重构的资源和参考资料
- 从重构联想到软件复用和技术传播
- 结语
- 参考文献
- 章节十四 重构工具
- 使用工具进行重构
- 重构工具的技术标准(Technical Criteria )
- 重构工具的实用标准(Practical Criteria )
- 小结
- 章节十五 集成
- 参考书目