php版32种设计模式
《 设计模式 》一书向我介绍了可能存在这种情况的概念。 当时,我仍在学习面向对象(OO),因此书中有很多概念很难理解。 但是,随着我对OO概念(尤其是接口和继承的使用)更加满意,我开始看到设计模式的真正价值。 作为应用程序开发人员,您可以终生工作,而无需知道调用任何模式或如何使用或何时使用它们。 但是,我发现对这些模式有很好的运用知识,以及在developerWorks文章“五种常见PHP设计模式”(请参阅参考资料 )中介绍的那些知识,您可以做两件事:
-
启用高带宽对话
- 如果您了解设计模式,则可以更快地构建可靠的OO应用程序。 但是,当您整个开发团队都知道各种模式时,您可能会突然进行非常高带宽的对话。 您不再需要讨论在此处或此处使用的所有类。 相反,您可以在模式方面互相交谈。 “好吧,我在这里引用一个单例,然后使用迭代器浏览我的对象的集合,并且...”比遍历组成这些模式的类,方法和接口要快得多。 仅凭这种沟通效率,就值得花时间花一些时间作为一个团队一起参加几次会议来研究模式。
-
减少痛苦的教训
- 每个设计模式都描述了一种解决常见问题的行之有效的方法。 因此,您不必担心设计是否正确,只要选择了可提供所需优势的模式即可。
陷阱
有一句话是这样的:“当您拿着锤子时,一切看起来都像钉子。” 当您发现自己认为很棒的模式时,可以尝试在任何地方(甚至在您不应该使用的地方)使用它。 请记住,您必须考虑正在学习的模式的使用目标,并且不要仅仅为了使用它们而将模式强加到应用程序的各个部分中。
本文介绍了五个可用于改进PHP代码的模式。 每个模式都涵盖一个特定的场景。 下载部分提供了这些模式PHP代码。
要求
要充分利用本文并使用示例,请在计算机上安装以下内容:
- PHP V5或更高版本(本文是使用PHP V5.2.4编写的)
- 提取程序,例如WinZIP(提取可下载的代码档案)
注意:尽管除了纯文本编辑器外,您绝对不需要其他编辑器,但我发现使用语法高亮显示和语法错误更正确实有帮助。 本文中的示例是使用Eclipse PHP开发工具(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.适配器模式的类图
![适配器模式的类图](https://i-blog.csdnimg.cn/blog_migrate/d6c14003a0cbd8b5ad5b23b7f4770b09.png)
替代方法
编写适配器的一种替代方法(有些人更喜欢这种适配器)是实现接口以适应行为,而不是扩展对象。 这是创建适配器的一种非常干净的方法,并且没有扩展对象的缺点。 使用该接口的缺点之一是您需要将实现添加到适配器类中,如图2所示。
图2.适配器模式(使用接口)
![适配器模式(使用接口)](https://i-blog.csdnimg.cn/blog_migrate/460c4f54a6524ed5ce4ca7a514172141.png)
迭代器模式
迭代器模式提供了一种封装对象集合或数组中的循环的方法。 如果您要遍历集合中不同类型的对象,则特别方便。
回顾清单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.迭代器模式的类图
![迭代器模式的类图](https://i-blog.csdnimg.cn/blog_migrate/4441731b60c0e783a50597fb038f1c60.png)
装饰图案
考虑清单4中的代码示例。该代码的目的是为“构建自己的汽车”网站的汽车添加一堆功能。 每种汽车型号都有更多功能和相应的成本。 仅使用两个模型,用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.装饰器模式的类图
![装饰器模式的类图](https://i-blog.csdnimg.cn/blog_migrate/6fb893d9129f2d6b0465f5e6df940c0f.png)
装饰器图案的一个优点是,您可以轻松地一次将多个装饰器固定到底座上。
如果您对流对象做了很多工作,则可以使用装饰器。 大多数流构造(例如输出流)都是装饰器,它们接受基本输入流,然后通过添加其他功能对其进行装饰,例如从文件输入流的一种,从缓冲区输入流的一种,等等。
委托模式
委托模式提供了一种基于不同条件委托行为的方式。 考虑清单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");
}
使用委托模式,对象通过在调用方法时为适当的对象设置内部引用来内部化此路由过程,如清单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.委托模式的类图
![委托模式的类图](https://i-blog.csdnimg.cn/blog_migrate/a8f680965ff6aa1ffdb98e58d4154b49.png)
状态模式
状态模式与命令模式相似,但是意图却大不相同。 考虑下面的代码。
清单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.状态模式的类图
![状态模式的类图](https://i-blog.csdnimg.cn/blog_migrate/ef3d2b308beefbcdc72ea024a5cb3df5.png)
摘要
在PHP代码中使用设计模式是使代码更具可读性和可维护性的一种方法。 通过使用已建立的模式,您将从通用的设计构造中受益,这些构造使团队中的其他开发人员可以了解您的代码的用途。 它还使您可以从其他设计师的工作中受益,因此您不必学习无法解决的设计想法的艰巨课程。
翻译自: https://www.ibm.com/developerworks/opensource/library/os-php-designpatterns/index.html
php版32种设计模式