在一个应用软件的成型过程中,一些意想不到的商业逻辑到处出现。比如,基于价格的考虑,这个任务必须减少项目;而那个任务也因为销售税而必须选择合适的比率;而其它的任务也必须因为其他的特别条件而终止。一些商业规则是简单的,只需要不到一两个布尔比较关系就够了,然而它的规则可能需要费时的估计,需要查询数据库或者用户输入数据来引导。
通过书写代码可以把抽象(比如一条商业规则)转化为具体可见的东西。但是抽象物(比如购物方式,税率,或者计算海运费等等)都有其进化的方式,而且这些改变很容易难倒一个不幸运的开发人员。为了保证安全可靠——到目前为止你在这本书所看到的——尽可能的分离封装那些容易改变的模块是个很完美的想法。而且,这的确也一个明智的应对商业规则的策略。
问题描述
有没有明确的方式来封装商业逻辑呢?有没有一个容易改写和重用的技术呢?
解决方案
规范模式是为验证和选择而开发的:
确认一个特殊的对象是否满足一定的标准
从集合中选择出满足给定标准的元素。
规范模式能让你有效的组织这些标准,并在你的应用程序中灵活的使用他们。
代码重构技术已经激发你的兴趣,你决定使用它来提升代码的清晰度和重用性。规范模式通过系统化进一步的深化了这一步,它系统把这个结构分解成一个个单独的对象,这些对象能够很方便的插入到你的应用程序的合适地方。很多情况下,在你的应用程序里,规范对象是参数化的,而且经常被组合在一起来构建复杂的合乎逻辑的表达式。
相关知识
Eric Evans 和 Martin Fowler 发表过一篇关于规范模型的文章,地址是:
http://www.martinfowler.com/apsupp/spec.pdf
这个模式在Eric Evans的书本《动态驱动设计》(“Domain Driven Design”)的第224到273页有详细的介绍。
为了合理的全面覆盖这个模式,这章被组织成合乎逻辑的三部分。第一部分通过一个纯粹的实例来说明基本的模式概念。(Evans 和 Fowler 把这个称为为“硬编码规范Hard Coded Specification”)。接下来的部分演示了如何构建一个参数化规范模型,它提供了一个更加动态和灵活的框架来实现规范模式(或者因此而称为“参数化规范”)的重用。最后一部分,我们开发了一个“方案工厂”(Policy Factory),它把许多规范对象集中成一个易于使用的包(package)。
Traveling to Warm Destinations(到温暖的目的地去旅行)
最近,我和我的家人计划去度一个假期,我的妻子想去一个“温暖的地方”。虽然有无数旅行相关的站点,但是在我们访问过的站点中没有一个站点能够为每一个目的地提供详细的天气信息。没办法,我们不得不转到weather.com然后开始搜索,这是十分的不方便的。现在让我们来改变这种情况,为一个假定的旅行站点增加一个天气搜索功能。在这里我们是用规范模式这个指南来引导你编码,从而比较旅行者期望的最低温度和许多目的地的平均温度
首先,我们创建一些非常简单的对象。第一个是旅行者(a Traveler),它存储了首选的最低温度。
// PHP5 class Traveler { public $min_temp; } |
接下来我们创建一个对象来表示目的地(Destination)。由于平均温度是一个关键的标准,目的地的构建函数(__constructor)应该得到一个十二维的数组,该数组的每一个值对应一年里面每个月的平均温度。
class Destination { protected $avg_temps; public function __construct($avg_temps) { $this->avg_temps = $avg_temps; } } |
目的地(Destination)同样也还要一个方法,通过调用这个方法能够得到这个目的地在指定月份的平均温度。
class Destination { //... public function getAvgTempByMonth($month) { $key = (int)$month - 1; if (array_key_exists($key, $this->avg_temps)) { return $this->avg_temps[$key]; } } } |
最后,一次旅行(类Trip)就由一个旅行者(类Traveler),一个目的地(类Destination)和一个日期(a Date)联合组成。
class Trip { public $date; public $traveler; public $destination; } |
给出上面这些对象,你就可以通过Trip::date得到旅行的月份,并且你能够比较目的地的月平均温度和旅行者期望的最低温度。(这个比较可能不是特别的复杂,但是你还是需要你自己亲自去实现)
让我们看看如何用规范模式实现“温暖目的地”的商业逻辑,并且看看如何应用这个模式来验证每一个目的地并选择出所有合适的目的地。
样本代码
规范模式的核心是一个带有IsSatisfiedBy()方法的对象,IsSatisfiedBy()方法接收一个变量来评估并且返回一个基于规范标准的布尔值。
“目的地是足够温暖的”的标准可能就是:
class TripRequiredTemperatureSpecification { public function isSatisfiedBy($trip) { $trip_temp = $trip->destination->getAvgTempByMonth( date(‘m’, $trip->date)); return ($trip_temp >= $trip->traveler->min_temp); } } |
下面是一些测试,用来检验这个规范是如何工作的。
一个最初的个体测试事例提供了一些目的地来一起工作:
class TripSpecificationTestCase extends UnitTestCase { protected $destinations = array(); function setup() { $this->destinations = array( ‘Toronto’ => new Destination( array(24, 25, 33, 43, 54, 63, 69, 69, 61, 50, 41, 29)) ,’Cancun’ => new Destination( array(74, 75, 78, 80, 82, 84, 84, 84, 83, 81, 78, 76)) ); } } |
(构造这些目的地(Destination)需要在实例化的时候输入一个包含每月平均温度的数组。做为一个美国的作者,在这些例子中我选择了华氏温度。对应的,Vicki期望的华氏温度70度等价于摄氏温度21度)
下一个测试构建了一个旅行者(Traveler),并且设置了它的首选最低温度和旅行日期同时也选择了一个目的地。这最初的组合“最低温度70度(华氏温度),目的地多伦多(Toronto),日期二月中旬”会和期望的一样,是不能通过的。
class TripSpecificationTestCase extends UnitTestCase { // ... function TestTripTooCold() { $vicki = new Traveler; $vicki->min_temp = 70; $toronto = $this->destinations[‘Toronto’]; $trip = new Trip; $trip->traveler = $vicki; $trip->destination = $toronto; $trip->date = mktime(0,0,0,2,11,2005); $warm_enough_check = new TripRequiredTemperatureSpecification; $this->assertFalse($warm_enough_check->isSatisfiedBy($trip)); } } |
但是,接下来的这个组合“70度,二月中旬,Cancun ”就会通过,和我们期望的一样。
class TripSpecificationTestCase extends UnitTestCase { // ... function TestTripWarmEnough() { $vicki = new Traveler; $vicki->min_temp = 70; $cancun = $this->destinations[‘Cancun’]; $trip = new Trip; $trip->traveler = $vicki; $trip->destination = $cancun; $trip->date = mktime(0,0,0,2,11,2005); $warm_enough_check = new TripRequiredTemperatureSpecification; $this->assertTrue($warm_enough_check->isSatisfiedBy($trip)); } } |
延伸阅读:
从魔兽看PHP设计模式
《PHP设计模式介绍》导言
PHP设计模式介绍 第一章 编程惯用法
PHP设计模式介绍 第二章 值对象模式
PHP设计模式介绍 第三章 工厂模式
PHP设计模式介绍 第四章 单件模式
PHP设计模式介绍 第五章 注册模式
PHP设计模式介绍 第六章 伪对象模式
PHP设计模式介绍 第七章 策略模式
PHP设计模式介绍 第八章 迭代器模式
PHP设计模式介绍 第九章 观测模式