快速业务通道

php教程:php设计模式介绍之规范模式

作者 佚名技术 来源 NET编程 浏览 发布时间 2012-03-15
m 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()则能返回指定名称的属性。

当你的应用程序必须加工处理一个表单,你可以先把 php教程:php设计模式介绍之规范模式 - 凌众科技

快速业务通道

php教程:php设计模式介绍之规范模式

作者 佚名技术 来源 NET编程 浏览 发布时间 2012-03-15
content

凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

分享到: 更多

Copyright ©1999-2011 厦门凌众科技有限公司 厦门优通互联科技开发有限公司 All rights reserved

地址(ADD):厦门软件园二期望海路63号701E(东南融通旁) 邮编(ZIP):361008

电话:0592-5908028 传真:0592-5908039 咨询信箱:web@lingzhong.cn 咨询OICQ:173723134

《中华人民共和国增值电信业务经营许可证》闽B2-20100024  ICP备案:闽ICP备05037997号

POST过来的数据值装载到数据源DataSource,然后使用参数化规范来完成表单的验证。(同样的方法也可以用到配置文件上,把配置文件的数据装载到DataSource数据源,然后使用规范来验证)。

现在,让我们构造一个参数化规范的示例类,主要用来搭建一个数据块。首先,我们要建一个规范,这个规范得满足“某一个字段和指定的值等价”的条件。

class FieldEqualSpecification {
protected $field;
protected $value;
public function __construct($field, $value) {
$this->field = $field;
$this->value = $value;
}
public function isSatisfiedBy($datasource) {
return ($datasource->get($this->field) == $this->value);
}
}

这个思路是很简单的:在构造(construction)期间,存储一个字段和它期望的值,从数据源(DataSource)中获取到期望的值传递到方法isSatisfiedBy(),然后比较。

为了测试这个规范,写一个测试实例来演示这个数据源(DataSource):

class SpecificationsTestCase extends UnitTestCase {
protected $ds;
function setup() {
$this->ds = new DataSource;
$this->ds->set(‘name’, ‘Jason’);
$this->ds->set(‘age’, 34);
$this->ds->set(‘email’, ‘jsweat_php@yahoo.com’);
$this->ds->set(‘sex’, ‘male’);
}
}

在上面的例子里,方法setup()创建了一个数据源对象,并设置了相应的属性。这个测试还包含一个方法,这个方法要么返回pass要么返回fail。

class SpecificationsTestCase extends UnitTestCase {
// ...
function TestFieldEqualSpecification() {
$name_jason = new FieldEqualSpecification(‘name’, ‘Jason’);
$this->assertTrue($name_jason->isSatisfiedBy($this->ds));
$sex_other = new FieldEqualSpecification(‘sex’, ‘other’);
$this->assertFalse($sex_other->isSatisfiedBy($this->ds));
}
}

通常在评估字符串的时候,一个正则表达式比一系列严格的比较关系能够更好的帮助你来定义你的需求。那么,让我们在FieldMatchSpecification中使用正则表达式来匹配我们的规范工具集。

class FieldMatchSpecification {
protected $field;
protected $regex;
public function __construct($field, $regex) {
$this->field = $field;
$this->regex = $regex;
}
public function isSatisfiedBy($datasource) {
return preg_match($this->regex, $datasource->get($this->field));
}
}

在这个例子里,这个字段对应的值和PCRE表达式都在构造的时候保存好了。然后方法isSatisfiedBy()从传递过来的数据源DataSource获取到这个字段,并且使用preg_match()方法来比较它的值是否满足对应的正则表达式。

下面这个例子演示了如何为FieldMatchSpecification字段匹配规范书写测试实例。

class SpecificationsTestCase extends UnitTestCase {
// ...
function TestFieldMatchSpecification() {
$valid_email = new FieldMatchSpecification(
‘email’,
‘/^[^\s@]+@[^\s.]+(?:\.[^\s.]+)+/’);
$this->assertTrue($valid_email->isSatisfiedBy($this->ds));
$name_ten_letters = new FieldMatchSpecification(
‘name’,
‘/^\w{10}$/’);
$this->assertFalse($name_ten_letters->isSatisfiedBy($this->ds));
}
}

上面例子中的email正则要求“在@前是一串不包含空格、@的字符,在@后是两组或者两组以上不包含可个空格或者句点的字符串,这些字符串之间由句点间隔着”。而变量$name_ten_letters规范要求输入的值必须恰好是由10个字符组成。

注:正则表达式
有许多书籍单独开一章来讲解正则表达式,甚至有些整本书都在讨论这个话题(译者注:作者是为了说明正则表达式的重要性和难度)。所以,请认识到上面的代码只是一个过于简单化的例子,而不是一个检查email的完善的正则表达式。

下面让我们搭建最后一个具体的规范用来检验一个字段值是否大于或者等于对应的值。我们毫无疑问的把它命名为FieldGreaterThanOrEqualSpecification.。

class FieldGreaterThanOrEqualSpecification {
protected $field;
protected $value;
public function __construct($field, $value) {
$this->field = $field;
$this->value = $value;
}
public function isSatisfiedBy($datasource) {
return ($datasource->get($this->field) >= $this->value);
}
}

这里没有太多的不可思议的:在构造器里面存储相应的要被比较的字段和值,然后在方法isSatisfiedBy()里面验证提取出来的字段。

下面是一个测试实例,用来演示如何应用FieldGreaterThanOrEqualSpecification。

class SpecificationsTestCase extends UnitTestCase {
// ...
function TestFieldGreaterThanOrEqualSpecification() {
$adult =
new FieldGreaterThanOrEqualSpecification(‘age’, 18);
$presidential_age =
new FieldGreaterThanOrEqualSpecification(‘age’, 35);
$this->assertTrue($adult->isSatisfiedBy($this->ds));
$this->assertFalse($presidential_age->isSatisfiedBy($this->ds));
}
}

你是否已经注意到在规范对象被标识上合理的名称时候,这些代码是如何开始证明自己的吗?你是否能够通过规范对象的名称来理解到代码的相应的功能? $adult->isSatisfiedBy($something)你看一眼就会明白,根本无需深入详细的钻研代码。这也是规范模式的优点之一吧。

到现在为止,你应该很清楚的明白规范模式就如一个接口。使用PHP5明确的表示出这个规范模式:

interface Specification {
public function isSatisfiedBy($datasource);
}

我们把这些基本的模块按照一定的格式集中起来,这甚至是非常有用的。因为从一个规范类的方法isSatisfiedBy()中返回的结果是一个布尔值(boolean),把这些布尔值运用到具体不同的规范类的效果都是相当好的。

为了实现逻辑与,我们创建一个类,把两个具体的规范实例类组合起来,当给出的数据源参数同时满足给定的条件后就返回true。


class AndSpecification implements Specification {
protected $spec;
protected $andSpec;
public function __construct($spec, $andSpec) {
$this->spec = $spec;
$this->andSpec = $andSpec;

function isSatisfiedBy($datasource) {
return ($this->spec->isSatisfiedBy($datasource)
&& $this->andSpec->isSatisfiedBy($datasource));
}

你也可以类似的实现逻辑或。


class OrSpecification implements Specification {
protected $spec;
protected $orSpec;
public function __construct($spec, $orSpec) {
$this->spec = $spec;
$this->orSpec = $orSpec;

function isSatisfiedBy($datasource) {
return ($this->spec->isSatisfiedBy($datasource)
|| $this->orSpec->isSatisfiedBy($datasource));
}

利用已经给出的“逻辑”规范和最初的一些规规范集合,你可以实现一些复杂的验证。

class PolicyFactory {
public function createJasonPolicy() {
$name_jason = new FieldEqualSpecification(‘name’, ‘Jason’);
$age_at_least_thirty =
new FieldGreaterThanOrEqualSpecification(‘age’, 30);
$male = new FieldEqualSpecification(‘sex’, ‘male’);
$jasons_email = new OrSpecification(
new FieldEqualSpecification(‘email’, ‘jsweat_php@yahoo.com’)
, new FieldEqualSpecification(‘email’,
‘jsweat@users.sourceforge.net’));
return new AndSpecification(
$name_jason, new AndSpecification(
$age_at_least_thirty,
new AndSpecification($male, $jasons_email)
));
}
}

一开始,策略工厂看起来有点零乱,主要是因为临时变量的数量阻止了单个具体规范的实例化。然而,代码中最有趣的部分是使用或规范(OrSpecification)和与规范(AndSpecification)类(在上面高亮显示了)。对email进行FieldEqualSpecification字段等价规范的两个实例化都被当作参数传递到或规范(OrSpecification)的构造方法中。因为或规范(OrSpecification)执行了规范接口,这个$jasons_email对象可以象任何其他具体的规范实例一样处理。事实上,在4行后它在又按照同样的方式被使用,具体在new AndSpecification($male, $jasons_email)。

使用上面给出的方法器PolicyFactor(上面的PolicyFactory),我们可以做到:

$jason = PolicyFactory::createJasonPolicy();
$jason->isSatisfiedBy($datasource);

这两行代码用于验证数据源$datasource的name字段的值是否是“jason”,它的age字段的值是否至少大于30而且它的email是否是jsweat_php@yahoo.com或者jsweat@users.sourceforge.net。

从审美观点上来说,所有在构建具体规范过程中所出现的中间变量都是令人不高兴的。那么,有没有方法来清除这些中间变量使得代码更加容易读和维护?是的,当然有!简单的利用PHP5的一个新特征来链接到对象的方法并返回这个方法。

具体方法的第一步可能是允许单个具体的规范知道如何“与”和“或”它自身。这可以通过引入Factory方法(具体请看第三章——工厂方法模式)来创建或规范(OrSpecification)和与规范(AndSpecification)对象。由于这些特点对所有的规范来说都是通用的,所以把它们封装到一个抽象的基类里绝对是个很好的主意。

abstract class BaseSpecification implements Specification {
protected $field;
public function and_($spec) { return new AndSpecification($this, $spec); }
public function or_($spec) { return new OrSpecification($this, $spec); }
}

这有趣的方法名字add_()和or_()是必须的,因为”add”和”or”在PHP中是关键字。

通过引入这个基类,那些在前面书写的具体的类都必须修改,得让它们都继承于BaseSpecification:

class FieldEqualSpecification extends BaseSpecification {
// ...}

接下来要引入Factory方法来更加容易的创建单个具体的规范类。这可能会在单独的factory类里面实现,但是为了更加方便,你可以把这个方法增加到PolicyFactory类里面。

class PolicyFactory {
protected function equal($field, $value) {
return new FieldEqualSpecification($field, $value);
}
protected function gTorEq($field, $value) {
return new FieldGreaterThanOrEqualSpecification($field, $value);
}
}

现在,让我们联合这些所有的Factory方法来创建一个综述,就象下面显示的一样:

class PolicyFactory {
// ..
public function createJasonPolicy() {
return $this->equal(‘name’, ‘Jason’)->and_(
$this->gTorEq(‘age’, 30)->and_(
$this->equal(‘sex’, ‘male’)->and_(
$this->equal(‘email’, ‘jsweat_php@yahoo.com’)->or_(
$this->equal(‘email’, ‘jsweat@users.sourceforge.net’)
))));
}
}

就和前面一样,方法createJasonPolicy()创建了一个策略(Policy),但是这代码的可读性好很多了。

在经过所有的重组后,类的图表就如下所示:

 

规范模式能让你在应用程序里更加容易的组织和架构你的商业逻辑。我把这个模式包含在这本书里面的一个原因就是因为它开始显示了在真正的显示世界应用中模式是如何修改和组合的。

凌众科技专业提供服务器租用、服务器托管、企业邮局、虚拟主机等服务,公司网站:http://www.lingzhong.cn 为了给广大客户了解更多的技术信息,本技术文章收集来源于网络,凌众科技尊重文章作者的版权,如果有涉及你的版权有必要删除你的文章,请和我们联系。以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!

分享到: 更多

Copyright ©1999-2011 厦门凌众科技有限公司 厦门优通互联科技开发有限公司 All rights reserved

地址(ADD):厦门软件园二期望海路63号701E(东南融通旁) 邮编(ZIP):361008

电话:0592-5908028 传真:0592-5908039 咨询信箱:web@lingzhong.cn 咨询OICQ:173723134

《中华人民共和国增值电信业务经营许可证》闽B2-20100024  ICP备案:闽ICP备05037997号