Copyright(c) DoYouHaoBaby.NET All Rights Reserved.
重构是小幅度进行的,其结果也必然是一系列小的改进。有时,会有很多次小的变更,每次给设计增加非常小的价值,有时,会有很
少的变更,但造成很大的差异。这就是突破。
我们会从一个粗糙的、浮浅的模型开始,然后基于对领域的深层理解以及对关注点的理解来细化它和设计。我们会对它增进新的概念
和抽象,然后执行设计的重构。每一次精化会让设计更清晰。这就建立好了突破的前提。
突破常包括思维上的变化,如同我们理解模型一样。它也是项目重要进程中的一个发起者,但它也有一些缺点。突破可能隐含了大量
的重构。这意味着需要时间和资源,而这两者看上去从来都不够。它也是有风险的,因为大量的重构会在应用中引入行为上的变化。
为达到一次突破,我们需要让隐式的概念显式化。当我们跟领域专家交谈时,我们交互了很多的思想和知识。某些概念成为了通用语
言,但也有些一直未被重视。它们是隐式概念,用来解释以及在领域中的其他的概念。在精化设计的过程中,某些隐式概念吸引了我
们注意力。我们发现它们当中的某些在设计中担当了重要的角色。因此我们需要将这些概念显式化。我们应该为它们建立类和关系。
当它们发生时,我们就拥有了突破的机会。
隐式概念可能不会仅于此。如果它们是领域概念,它们可能被引入模型和设计中。我们应该如何识别它们呢?第一种发现隐含概念的
方式是倾听话语。我们在建模和设计过程中使用的话语包含了许多关于领域的信息。开始时可能不会太多,或者某些信息没有被正确
地使用。某些概念可能不能被完全理解,或者理解完全是错误的。这是在学习一个新的领域所必须经历的。但因为我们构建了我们的
通用语言,关键概念会被加入其中。在那里我们可以开始查找隐含概念。
有时设计的部分可能不会那么清晰,一堆关系会让路径的计算变得难以进行,或者计算的过程会复杂到难以理解。这在设计中显得极
其笨拙,但这也是寻找隐藏的概念的绝佳之所,可能我们错过了什么。如果某个关键概念在破解谜团时缺失了,其他的事物就不得不
替代它完成它的功能。这会让某些对象变胖,给它们增加了一些本不应该属于它的行为。设计的清晰度受到了破坏。努力寻找是否有
缺失的概念,如果找到一个,就把它显式化。对设计进行重构让它更简单更具柔性。
当我们构建知识时很可能会遭遇到矛盾。某个领域专家所讲的看上去跟另一个领域专家所持的观点发生了矛盾。一个需求可能看上去
跟另一个需求矛盾。某些这样的矛盾其实不是真正的矛盾,只是因为看待同一事物的方式不同,或者只是因为讲解时缺乏了精确度。
我们应该努力去解决矛盾,有时这确实会帮助我们发现重要的概念。即使并没有发现它们,能够保持所有事物清晰也是很重要的。
其他明细的挖掘模型概念的方式是使用领域文献。现在有众多为几乎任何可能的主题而编写的书,它们包含了许多关于特定领域的知
识。这些书通常不包含要展现的领域的模型,它们包含的信息需要进一步处理、精炼并细化。但是,在书中发现的信息是有价值的,
会给我们提供对领域的深层视图。
在让概念显式化时,还有其他一些非常有用的概念:约束、过程和规约。约束是一个很简单的表示不变量的方式。无论对象的数据发
生了变化,都要考虑不变量。这可以简单地通过把不变量的逻辑放置在一个约束中去实现它。下面是一个简单的案例,其目的是为了
解释这个概念,而不是为了表现对相似案例的建议方法。

我们可以向一个书架添加书籍,但是我们永远不能增加超过它容量的部分。这儿可以被视为书架行为的一部分,见下面的PHP 代码。
- class Bookshelf {
- private $capacity = 20;
- private $content;
- public function add( $book ) {
- if( $this->content->size() + 1 <= $this->capacity) {
- $this->content->add( $book );
- }
- else {
- throw new Exception(“The bookshelf has reached its limit.”);
- }
- }
- }
我们可以重构这个代码,将约束提取为一个单独的方法。
- class Bookshelf {
- private $capacity = 20;
- private $content;
- public function add( $book ) {
- if( $this-> isSpaceAvailable()) {
- $this->content->add( $book );
- }
- else {
- throw new Exception(“The bookshelf has reached its limit.”);
- }
- }
- private function isSpaceAvailable() {
- return $this->content->size() + 1 <= $this->capacity;
- }
- }
将约束置于一个单独的方法让它显示化有很多优点。它很容易阅读,并且每个人都会注意到add()方法从属于这个约束。如果约
束变得更复杂,这可以为向该方法增加更多逻辑提供增长空间。
过程通常在代码中被表现为procedure。从我们开始使用面向对象语言后我们就不再用一个过程化的方法,所以我们需要为过程选择一
个对象,然后给它增加行为。最好的实现过程的方式是使用服务。其他的处理过程的不同的方式如,将算法封装进一个策略对象。并
不是所有的过程都必须显式化。如果通用语言中提到了某个过程,那就是将它显式实现的时机了。
我们在此要介绍的最后一个将概念显式化的方法是规约。简单得说,规约是用来测试一个对象是否满足特定条件的。
领域层包含了应用到实体和值对象上的业务规则。那些规则通常与它们要应用到的对象合成一体。在这些规则中,某些只是用来回答
“是”和“否”的一组问题的,某些规则可以被表现成一系列操作布尔值的逻辑上的操作,最终结果也是一个布尔值。一个这样的案
例是在一个客户对象上执行测试,看它是否符合特定的信用条件。这个规则可以被表现成一个方法,起名叫isEligible(),并且可以
附加到客户对象上。但这个规则不是严格基于客户的数据进行操作的一个简单的方法。评估规则涉及到验证客户的信用,检查他过去
是否支付过他的债务,检查他是否具有足够的余额等。这样的业务规则可能非常的大,非常复杂,让对象的功能肿胀,不再满足其原
始的目的。在这种情况下我们可能会试图将整个规则移动到应用层,因为它看上去已经超越了领域层了。实际上,到了重构的时候了。
规则应该被封装到一个负责它的对象中,这将成为客户的规约,并且被保留在领域层中。新的对象将包含一系列布尔方法,这些方法
用来测试一个客户对象是否符合某种信用。每一个方法担负了一个小的测试的功能,所有的方法可以通过组合对某个原始问题给出答
案。如果业务规则不能被包含到一个规约对象中,对应的代码会遍布到无数的对象中,让它不再一致。
规约用来测试对象是否满足某种需要,或者他们是否已经为某种目的准备完毕。它也可以被用来从一个集合中筛选一个特定的对象,
或者作为某个对象的创建条件。
通常情况下,一个单个的规约负责检查是否满足一个简单的规则,若干个这样的规约组合在一起表现一个复杂的规约,例如:
- $customer = $customerRepository->findCustomer( $customerIdentiy );
- $customerEligibleForRefund =new Specification( new CustomerPaidHisDebtsInThePast(),new CustomerHasNoOutstandingBalances() );
- if( $customerEligibleForRefund->isSatisfiedBy( $customer) {
- $refundService->issueRefundTo( $customer );
- }
测试简单的规则变得非常简单,只需阅读这段代码,这段代码很明显地告诉我们一个客户是否符合偿还条件的真正含义。
