首 页   · 站长博客 · 用户注册 · 会员登陆  · 会员排行  ·最新主题  ·最近回复  精华区  版权声明  ·论坛管理
  当前登录身份:游客,请先登录。  笔名: 口令: 验证码:   
楼 主  index »  PHP相关资源下载区 » 《PHP设计模式介绍》第十章 规范模式  


  作者:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

  lxshark@yeah.net..
  593993745
  hi.baidu.com/mythicsky

 

 发表:2009-08-08 20:52:00 阅读 1946 次 回复 1 次 得分0  |   字号 字色
《PHP设计模式介绍》第十章 规范模式
在一个应用软件的成型过程中,一些意想不到的商业逻辑到处出现。比如,基于价格的考虑,这个任务必须减少项目;而那个任务也因为销售税而必须选择合适的比率;而其它的任务也必须因为其他的特别条件而终止。一些商业规则是简单的,只需要不到一两个布尔比较关系就够了,然而它的规则可能需要费时的估计,需要查询数据库或者用户输入数据来引导。

通过书写代码可以把抽象(比如一条商业规则)转化为具体可见的东西。但是抽象物(比如购物方式,税率,或者计算海运费等等)都有其进化的方式,而且这些改变很容易难倒一个不幸运的开发人员。为了保证安全可靠——到目前为止你在这本书所看到的——尽可能的分离封装那些容易改变的模块是个很完美的想法。而且,这的确也一个明智的应对商业规则的策略。

问题描述

有没有明确的方式来封装商业逻辑呢?有没有一个容易改写和重用的技术呢?

解决方案

规范模式是为验证和选择而开发的:

确认一个特殊的对象是否满足一定的标准

从集合中选择出满足给定标准的元素。

规范模式能让你有效的组织这些标准,并在你的应用程序中灵活的使用他们。

代码重构技术已经激发你的兴趣,你决定使用它来提升代码的清晰度和重用性。规范模式通过系统化进一步的深化了这一步,它系统把这个结构分解成一个个单独的对象,这些对象能够很方便的插入到你的应用程序的合适地方。很多情况下,在你的应用程序里,规范对象是参数化的,而且经常被组合在一起来构建复杂的合乎逻辑的表达式。

相关知识

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));
}


参数化规范

Trip Required Temperature Specification必须很熟悉Trip对象的结构,并且钻研Trip对象的三个public(公开)属性。这并不是很糟糕的。事实上,在过去使用规范模式的过程中,我发现不少规范得益于我对特有参数对象的详细了解。然而,这种紧密的联系对规范的重用带来了很大的麻烦。

幸运的是,各种不同的规范模式已经开始着手从事代码重用问题的研究。特别值得提出的是,参数化规范模式通过构造器(constructor)来接收参数,这个参数主要用于确定isSatisfiedBy()函数的进化标准。

现在让我们看看这个参数化规范,它使用了相同旅行站点的对象。假定你要搜索出一个目的地的列表,并且列表显示出满足“足够温暖”标准的城市。

使用原来的Trip Required Temperature Specification,你不得不为每个评估创建一个Trip对象。这是因为(在这个明确的问题上)旅行者(Traveler)和旅行日期(Date)是不变的,仅仅只有目的地因为你不断的重述可行目的地列表而不断变化。

使用参数化规范,你记住了旅行者首选的温度和旅行日期,并且你只要通过传递一个变量到方法isSatisfiedBy()就可以比较目的地。

参数化规范对象Destination Required Temperature Specification的构造函数需要一个旅行者(Traveler)和一个日期(Date)来实例化这个规范。


class DestinationRequiredTemperatureSpecification {
protected $temp;
protected $month;
public function __construct($traveler, $date) {
$this->temp = $traveler->min_temp;
$this->month = date(‘m’, $date);
}



由于存储在实例化变量中的数据(温度和日期)的一致性,Destination Required Temperature Specification的方法isSatisfiedBy()把目的地(Destination)做为一个输入参数来评估。


class DestinationRequiredTemperatureSpecification {
// ...
function isSatisfiedBy($destination) {
return
($destination->getAvgTempByMonth($this->month) >= $this->temp);
}



现在你可以写一个测试实例来过滤目的地列表。


class DestinationSpecificationTestCase extends UnitTestCase {
// similar setup to TripSpecificationTestCase
function TestFindingDestinations() {
$this->assertEqual(2, count($this->destinations));
$valid_destinations = array();
$vicki = new Traveler;
$vicki->min_temp = 70;
$travel_date = mktime(0,0,0,2,11,2005);
$warm_enough = new DestinationRequiredTemperatureSpecification(
vicki, $travel_date);
foreach($this->destinations as $dest) {
if ($warm_enough->isSatisfiedBy($dest)) {
$valid_destinations[] = $dest;
}
}
$this->assertEqual(1, count($valid_destinations));
$this->assertIdentical(
$this->destinations[‘Cancun’],
$valid_destinations[0]);
}
}
 
通过上面的例子,你可以看到参数化规范能给你带来更多额外的自由和灵活性。 

现在让我们看看另外一个例子,在这个例子里面数据类型和规范都必须是非常的灵活易变的。

在web应用程序中,最常用也最让人发狂的一个问题就是确认表单输入(对表单输入数据的验证)。在软件开发过程中(甚至在开发过程外),表单是经常改变得,而且在大型的或者功能丰富的应用程序里面,表单的数量能够很快的增长。你可以创建一个唯一的对象来封装每一个表单,然后使用规范模式来确认每一个对象,但这绝对是一个持续的恶梦。

有没有一个方便的数据类型能够很容易的适应任何一个表单吗?如果有,那么有没有一个能够确认动态数据类型的方式呢?

上面每一个问题的答案显然是肯定的。

WEB应用程序组件工具箱(简称WACT)的数据源(DataSource)接口能够get(获取),set(设置),,并且动态的创建对象属性(有点类似PHP4的_GET()和_SET()方法),这些对象属性就是表单的封装操作手柄。(熟悉JAVA的读者可以把一个数据源(DataSource)理解未JAVA中的哈希图(HashMap))同时,参数化规范模式提供了一个模型以一定的标准来验证一个数据源。

注:WACT

WACT,WEB应用程序组件工具箱,能够在SourceForge(http://wact.sf.net)上下载到。它是一个PHP库类,用来解决在WEB应用程序开发中一些常用的问题。WACT很注重代码重用、单体测试和使用设计模式方面的技术。WACT理论相关的信息位于http://wact.sf.net/index.php/datasource

WACT数据源(DataSource)类包含在这本书的源代码里面,这样你可以方便的测试这些代码。

在这个例子中,数据源(DataSource)类可以被认为如下面的代码所示,它几乎和第5章开发的Register(注册)类完全一样。


class DataSource {
protected $store = array();
function get($key) {
if (array_key_exists($key, $this->store))
return $this->store[$key];
}
function set($key, $val) {
$this->store[$key] = $val;
}



数据源(DataSource)直接通过一串标识符来获取一个对象的属性。方法Set()用来改变原有的属性或者动态的创建新的属性,方法get()则能返回指定名称的属性。
 
 
 1#楼  
 
  回复人:lxshark
  注册时间:2009-08-06
  主题/回复:49/50
  积分:639
  等级:★★★(六级)
  称号:声名鹊起

   
 1#楼 发表于2009-08-08 21:34:18  评分:× 

回复给楼主(lxshark)
希望能对你们更好的学习PHP有所帮助,也期望大家有时间多光顾我的
点此打开链接:http://hi.baidu.com/mythicsky
  页数1/1首页 « 1 » 末页
  发表回复:您还没有登陆,无法发表回复。请先[登陆]

一起PHP技术联盟 主办:一起PHP 联系方式:站长QQ:4304410 QQ群:8423742 20159565 站长博客 E-mail: nqp@nqp.me 执行时间:0.013sec
SimsmaBBS 2008 (v6.0) Developed by 17php.com,Copyright(C)2003-2010 All rights reserved. 副本授权:一起PHP官方专用版