另外五个 PHP 设计模式

设计模式 一书介绍了不少此类概念。当时,我还在学习面向对象 (OO),所以我发现那本书中有许多概念都很难领会。可是,随着愈来愈熟悉 OO 概念 —— 尤为是接口和继承的使用 —— 我开始看到设计模式中的实际价值。做为一名应用程序开发人员,即便从不了解任何模式或者如何及什么时候使用这些模式,对您的职业生涯也没有什么大的影响。可是,我发现了解这些模式以及 developerWorks 文章 “五种常见 PHP 设计模式” 中介绍的那些模式的优秀知识后(请参阅 参考资料),您能够完成两件事情:php

  • 启用高带宽会话设计模式

  • 若是了解设计模式,您将可以更快地构建可靠的 OO 应用程序。但当整个开发团队知道各类模式时,您能够忽然拥有很是高的带宽会话。您再也不须要讨论将处处使用的全部类。相反,您能够与其余人谈论模式。“我要在这里引用一个单例(singleton),而后使用迭代器遍历对象集合,而后……” 比遍历构成这些模式的类、方法和接口快不少。单是通讯效率一项就值得花时间以团队的形式经过会话来研究模式。数组

  • 减小痛苦的教训编辑器

  • 每一个设计模式都描述了一种通过验证的解决常见问题的方法。所以,您无需担忧设计是否是正确的,只要您已经选择了提供所需优势的模式。模块化

缺陷

有句谚语说得好:“当您手中拿着一把锤子时,全部事物看上去都像钉子”。当您认为本身找到一个优秀模式时,您可能会尝试处处使用它,即便在不该当使用它的位置。记住您必须考虑正在学习的模式的使用目的,不要为了使用模式而把这些模式强行应用到应用程序的各个部分中。学习

本文将介绍可用于改进 PHP 代码的五个模式。每一个模式都将介绍一个特定场景。能够在 下载 部分中得到这些模式的 PHP 代码。ui

要求

要发挥本文的最大功效并使用示例,须要在计算机中安装如下软件:this

  • PHP V5 或更高版本(本文是使用 PHP V5.2.4 撰写的)spa

  • 压缩程序,例如 WinZIP(用于压缩可下载的代码归档)操作系统

注:虽然您也可使用纯文本编辑器,可是我发现拥有语法高亮显示和语法纠错功能的编辑器真的颇有帮助。本文中的示例是使用 Eclipse PHP Development Tools (PDT) 编写的。

适配器模式

在须要将一类对象转换成另外一类对象时,请使用适配器模式。一般,开发人员经过一系列赋值代码来处理此过程,如清单 1 所示。适配器模式是整理此类代码并在其余位置重用全部赋值代码的优秀方法。此外,它还将隐藏赋值代码,若是同时还要设定格式,这样能够极大地简化工做。

清单 1. 使用代码在对象之间赋值
class AddressDisplay
{
    private $addressType;
    private $addressText;

    public function setAddressType($addressType)
    {
        $this->addressType = $addressType;
    }

    public function getAddressType()
    {
        return $this->addressType;
    }

    public function setAddressText($addressText)
    {
        $this->addressText = $addressText;
    }

    public function getAddressText()
    {
        return $this->addressText;
    }
}

class EmailAddress
{
    private $emailAddress;
    
    public function getEmailAddress()
    {
        return $this->emailAddress;
    }
    
    public function setEmailAddress($address)
    {
        $this->emailAddress = $address;
    }
}

$emailAddress = new EmailAddress();
/* Populate the EmailAddress object */
$address = new AddressDisplay();/* Here's the assignment code, where I'm assigning values 
  from one object to another... */$address->setAddressType("email");
$address->setAddressText($emailAddress->getEmailAddress());

此示例将使用 AddressDisplay 对象把地址显示给用户。AddressDisplay 对象有两部分:地址类型和一个格式化的地址字符串。

在实现模式(参见清单 2)后,PHP 脚本将再也不须要担忧如何把 EmailAddress 对象转换成 AddressDisplay 对象。那是件好事,尤为是在AddressDisplay 对象发生更改时或者控制如何把 EmailAddress 对象转换成 AddressDisplay 对象的规则发生更改时。记住,以模块化风格设计代码的主要优势之一就是,在业务领域发生一些更改时或者须要向软件中添加新功能时尽量少的使用更改。即便在执行普通任务(例如把一个对象的属性值赋给另外一个对象)时,也请考虑使用此模式。

清单 2. 使用适配器模式
class EmailAddressDisplayAdapter extends AddressDisplay
{
    public function __construct($emailAddr)
    {
        $this->setAddressType("email");
        $this->setAddressText($emailAddr->getEmailAddress());
    }
}	

$email = new EmailAddress();
$email->setEmailAddress("user@example.com");

$address = new EmailAddressDisplayAdapter($email);

echo($address->getAddressType() . "\n") ;
echo($address->getAddressText());

图 1 显示了适配器模式的类图。

图 1. 适配器模式的类图

适配器模式的类图

替代方法

编写适配器的替代方法 —— 而且是推荐方法 —— 是实现一个接口来修改行为,而不是扩展对象。这是一种很是干净的、建立适配器的方法而且没有扩展对象的缺点。使用接口的缺点之一是须要把实现添加到适配器类中,如图 2 所示:

图 2. 适配器模式(使用接口)

适配器模式(使用接口)

回页首

迭代器模式

迭代器模式将提供一种经过对象集合或对象数组封装迭代的方法。若是须要遍历集合中不一样类型的对象,则使用这种模式尤其便利。

查看上面清单 1 中的电子邮件和物理地址示例。在添加迭代器模式以前,若是要遍历我的地址,则可能要遍历物理地址并显示这些地址,而后遍历我的电子邮件地址并显示这些地址,而后遍历我的 IM 地址并显示这些地址。很是复杂的遍历!

相反,经过实现迭代器,您只须要调用 while($itr->hasNext()) 并处理下一个条目 $itr->next() 返回。清单 3 中显示了一个迭代器示例。迭代器功能强大,由于您能够添加要遍历的新类型条目,而且无需更改遍历条目的代码。例如,在 Person 示例中,能够添加 IM 地址数组;只需更新迭代器,无需更改遍历地址的任何代码。

清单 3. 使用迭代器模式遍历对象
class PersonAddressIterator implements AddressIterator
{
    private $emailAddresses;
    private $physicalAddresses;
    private $position;
    
    public function __construct($emailAddresses)
    {
        $this->emailAddresses = $emailAddresses;
        $this->position = 0;
    }
    
    public function hasNext()
    {
        if ($this->position >= count($this->emailAddresses) || 
            $this->emailAddresses[$this->position] == null) {
            return false;
        } else {
            return true;
        }
    }
    
    public function next()
    {
        $item = $this->emailAddresses[$this->position];
        $this->position = $this->position + 1;
        return $item;
    }
    
}

若是把 Person 对象修改成返回 AddressIterator 接口的实现,则在将实现扩展为遍历附加对象时无需修改使用迭代器的应用程序代码。您可使用一个混合迭代器,它封装了遍历清单 3 中列出的每种地址的迭代器。本文提供了此类应用示例(请参阅 下载)。

图 3 显示了迭代器模式的类图。

图 3. 迭代器模式的类图

迭代器模式的类图

回页首

装饰器 (decorator) 模式

考虑清单 4 中的代码样例。这段代码的目的是要把许多功能添加到 Build Your Own Car 站点的汽车中。每一个汽车模型都有更多功能及相关价格。若是只针对两个模型,使用 if then 语句添加这些功能十分日常。可是,若是出现了新模型,则必须返回查看代码并确保语句对新模型工做正常。

清单 4. 使用装饰器模式添加功能
require('classes.php');

$auto = new Automobile();

$model = new BaseAutomobileModel();

$model = new SportAutomobileModel($model);

$model = new TouringAutomobileModel($model);

$auto->setModel($model);

$auto->printDescription();

进入装饰器模式,该模式容许您经过一个优秀整洁的类将此功能添加到 AutomobileModel。每一个类仅仅关注其价格、选项以及添加到基本模型的方式。

图 4 显示了装饰器模式的类图。

图 4. 装饰器模式的类图

装饰器模式的类图

装饰器模式的优势是能够轻松地同时跟踪库的多个装饰器。

若是您拥有流对象的使用经验,则必定使用过装饰器。大多数流结构(例如输出流)都是接受基本输入流的装饰器,而后经过添加附加功能来装饰它 —— 例如从文件输入流、从缓冲区输入流,等等。

回页首

委托模式

委托模式将提供一种基于各类条件委托行为的方法。考虑清单 5 中的代码。这段代码包含几个条件。根据条件,代码将选择相应类型的对象来处理请求。

清单 5. 使用条件语句来发送送货请求
pkg = new Package("Heavy Package");
$pkg->setWeight(100);

if ($pkg->getWeight() > 99)
{
	echo( "Shipping " . $pkg->getDescription() . " by rail.");
} else {
	echo("Shipping " . $pkg->getDescription() . " by truck");
}

使用委托模式,对象将内在化(internalize)此发送过程,方法为在调用如清单 6 中的 useRail() 之类的方法时设置对相应对象的内部引用。若是处理各个包的条件发生更改或者使用新的送货类型时,则使用此模式尤其便利。

清单 6. 使用委托模式来发送送货请求
require_once('classes.php');

$pkg = new Package("Heavy Package");
$pkg->setWeight(100);

$shipper = new ShippingDelegate();

if ($pkg->getWeight() > 99)
{
	$shipper->useRail();
}

$shipper->deliver($pkg);

委托将经过调用 useRail() 或 useTruck() 方法来切换处理工做的类,从而提供动态更改行为的优势。

图 5 显示了委托模式的类图。

图 5. 委托模式的类图

委托模式的类图

回页首

状态模式

状态模式相似于命令模式,可是意图大相径庭。考虑下面的代码。

清单 7. 使用代码来构建机器人
class Robot 
{

	private $state;

	public function powerUp()
	{
		if (strcmp($state, "poweredUp") == 0)
		{
			echo("Already powered up...\n");
			/* Implementation... */
		} else if ( strcmp($state, "powereddown") == 0) {
			echo("Powering up now...\n");
			/* Implementation... */
		}
	}

	public function powerDown()
	{
		if (strcmp($state, "poweredUp") == 0)
		{
			echo("Powering down now...\n");
			/* Implementation... */
		} else if ( strcmp($state, "powereddown") == 0) {
			echo("Already powered down...\n");
			/* Implementation... */
		}
	}

	/* etc... */

}

在此清单中,PHP 代码表示变成一辆汽车的强大机器人的操做系统。机器人能够启动、关闭、由汽车变成机器人以及由机器人变成汽车。代码现已就绪,可是您会看到若是任何规则发生更改或者添加另外一个状态则会变得十分复杂。

如今查看清单 8,其中提供了相同的逻辑处理机器人的状态,可是这一次把逻辑放入状态模式。清单 8 中的代码完成的工做与初始代码相同,可是用于处理状态的逻辑已经被放入每一个状态的一个对象中。为了演示使用设计模式的优势,假定不久之后,这些机器人发现它们不该在处于机器人模式时关闭。实际上,若是它们关闭,它们必须先切换到汽车模式。若是它们已经处于汽车模式下,则机器人将关闭。使用状态模式,对代码的更改十分微小。

清单 8. 使用状态模式处理机器人的状态
$robot = new Robot();
echo("\n");
$robot->powerUp();
echo("\n");
$robot->turnIntoRobot();
echo("\n");
$robot->turnIntoRobot(); /* This one will just give me a message */
echo("\n");
$robot->turnIntoVehicle();
echo("\n");
清单 9. 对一个状态对象的微小更改
class NormalRobotState implements RobotState
{
    private $robot;

    public function __construct($robot)
    {
        $this->robot = $robot;
    }

    public function powerUp()
    {
        /* implementation... */
    }
    public function powerDown()  
    {        /* First, turn into a vehicle */
        $this->robot->setState(new VehicleRobotState($this->robot));
        $this->robot->powerDown();
    }
    
    public function turnIntoVehicle()  
    {
        /* implementation... */
    }
    
    public function turnIntoRobot() 
    {
        /* implementation... */
    }
}

图 6 中一个不太明显的地方就是状态模式中的每一个对象都有对上下文对象(机器人)的引用,所以每一个对象均可以把状态提高到相应的状态。

图 6. 状态模式的类图

状态模式的类图

回页首

结束语

在 PHP 代码中使用设计模式可使代码更容易阅读、更易维护。经过使用已经创建的模式,您将从通用的设计结构中获益,从而容许团队的其余开发人员了解代码的意图。它还使您能够从其余设计者完成的工做中获益,所以无需从失败的设计理念中吸收教训。

相关文章
相关标签/搜索