用较小的示例展示模式的代价是非常困难的。这每每有些大材小用的意味,由于模式实际上是在大型代码库中发挥浸染的。本文不展示大型运用程序,以是您须要思虑的是在您自己的大型运用程序中运用示例事理的方法 —— 而不是本文演示的代码本身。这不是说您不应该在小运用程序中利用模式。很多良好的运用程序都以小运用程序为出发点,逐渐发展到大型运用程序,以是没有情由不以此类踏实的编码实践为根本。
既然您已经理解了设计模式以及它们的有用之处,现在我们来看看 PHP V5 的五种常用模式。
一、工厂模式最初在设计模式 一书中,许多设计模式都鼓励利用疏松耦合。要理解这个观点,让我们最好谈一下许多开拓职员从事大型系统的艰巨进程。在变动一个代码片段时,就会发生问题,系统其他部分 —— 您曾认为完备不干系的部分中也有可能涌现级联毁坏。
该问题在于紧密耦合 。系统某个部分中的函数和类严重依赖于系统的其他部分中函数和类的行为和构造。您须要一组模式,使这些类能够相互通信,但不肯望将它们紧密绑定在一起,以避免涌现联锁。
在大型系统中,许多代码依赖于少数几个关键类。须要变动这些类时,可能会涌现困难。例如,假设您有一个从文件读取的 User 类。您希望将其变动为从数据库读取的其他类,但是,所有的代码都引用从文件读取的原始类。这时候,利用工厂模式会很方便。
工厂模式 是一种类,它具有为您创建工具的某些方法。您可以利用工厂类创建工具,而不直策应用 new。这样,如果您想要变动所创建的工具类型,只需变动该工厂即可。利用该工厂的所有代码会自动变动。
清单 1 显示工厂类的一个示列。等式的做事器端包括两个部分:数据库和一组 PHP 页面,这些页面许可您添加反馈、要求反馈列表并获取与特定反馈干系的文章。
清单 1. Factory1.php
<?phpinterface IUser{ function getName();} class User implements IUser{ public function __construct($id) { } public function getName() { return \公众Jack\公众; }} class UserFactory{ public static function Create($id) { return new User($id); }} $uo = UserFactory::Create(1);echo($uo->getName() . \"大众\"大众);?>
IUser 接口定义用户工具应实行什么操作。IUser 的实现称为 User,UserFactory 工厂类则创建 IUser 工具。此关系可以用图 1 中的 UML 表示
如果您利用 php 阐明器在命令行上运行此代码,将得到如下结果:
php factory1.phpJack
测试代码会向工厂要求 User 工具,并输出 getName 方法的结果。
有一种工厂模式的变体利用工厂方法。类中的这些公共静态方法布局该类型的工具。如果创建此类型的工具非常主要,此方法非常有用。例如,假设您须要先创建工具,然后设置许多属性。此版本的工厂模式会将该进程封装在单个位置中,这样,不用复制繁芜的初始化代码,也不必将复制好的代码在在代码库中到处粘贴。
清单 2 显示利用工厂方法的一个示例。
清单 2. Factory2.php
<?phpinterface IUser{ function getName();} class User implements IUser{ public static function Load($id) { return new User($id); } public static function Create() { return new User(null); } public function __construct($id) { } public function getName() { return \"大众Jack\公众; }} $uo = User::Load(1);echo($uo->getName() . \"大众\公众);?>
这段代码要大略得多。它仅有一个接口 IUser 和一个实现此接口的 User 类。User 类有两个创建工具的静态方法。此关系可用图 2 中的 UML 表示。
图 2. IUser 接口和带有工厂方法的 user 类
在命令行中运行脚本产生的结果与清单 1 的结果相同,如下所示:
php factory2.phpJack
如上所述,有时此类模式在规模较小的环境中彷佛有些大材小用。不过,最好还是学习这种踏实的编码形式,以便运用于任意规模的项目中。
二、单元素模式某些运用程序资源是独占的,由于有且只有一个此类型的资源。例如,通过数据库句柄到数据库的连接是独占的。您希望在运用程序中共享数据库句柄,由于在保持连接打开或关闭时,它是一种开销,在获取单个页面的过程中更是如此。
单元素模式可以知足此哀求。如果运用程序每次包含且仅包含一个工具,那么这个工具便是一个单元素(Singleton)。清单 3 中的代码显示了 PHP V5 中的一个数据库连接单元素。
清单 3. Singleton.php
此代码显示名为 DatabaseConnection 的单个类。您不能创建自已的 DatabaseConnection,由于布局函数是专用的。但利用静态 get方法,您可以得到且仅得到一个 DatabaseConnection 工具。此代码的 UML 如图 3 所示。
图 3. 数据库连接单元素
在两次调用间,handle 方法返回的数据库句柄是相同的,这便是最好的证明。您可以在命令行中运行代码来不雅观察这一点。
php singleton.phpHandle = Object id #3Handle = Object id #3
返回的两个句柄是同一工具。如果您在全体运用程序中利用数据库连接单元素,那么就可以在任何地方重用同一句柄。
您可以利用全局变量存储数据库句柄,但是,该方法仅适用于较小的运用程序。在较大的运用程序中,应避免利用全局变量,并利用工具和方法访问资源。
三、不雅观察者模式不雅观察者模式为您供应了避免组件之间紧密耦合的另一种方法。该模式非常大略:一个工具通过添加一个方法(该方法许可另一个工具,即不雅观察者 注书籍身)使本身变得可不雅观察。当可不雅观察的工具变动时,它会将发送到已注册的不雅观察者。这些不雅观察者利用该信息实行的操作与可不雅观察的工具无关。结果是工具可以相互对话,而不必理解缘故原由。
一个大略示例是系统中的用户列表。清单 4 中的代码显示一个用户列表,添加用户时,它将发送出一条。添加用户时,通过发送的日志不雅观察者可以不雅观察此列表。
清单 4. Observer.php
<?phpinterface IObserver{ function onChanged($sender, $args);} interface IObservable{ function addObserver($observer);} class UserList implements IObservable{ private $_observers = array(); public function addCustomer($name) { foreach($this->_observers as $obs) { $obs->onChanged($this, $name); } } public function addObserver($observer ) { $this->_observers[] = $observer; }} class UserListLogger implements IObserver{ public function onChanged($sender, $args) { echo(\公众'$args' added to user list\"大众); }} $ul = new UserList();$ul->addObserver(new UserListLogger());$ul->addCustomer(\公众Jack\"大众);?>
此代码定义四个元素:两个接口和两个类。IObservable 接口定义可以被不雅观察的工具,UserList 实现该接口,以便将本身注册为可不雅观察。IObserver 列表定义要通过若何的方法才能成为不雅观察者,UserListLogger 实现 IObserver 接口。图 4 的 UML 中展示了这些元素。
图 4. 可不雅观察的用户列表和用户列表事宜日志程序
如果在命令行中运行它,您将看到以下输出:
php observer.php'Jack' added to user list
测试代码创建 UserList,并将 UserListLogger 不雅观察者添加到个中。然后添加一个消费者,并将这一变动关照 UserListLogger。
认识到 UserList 不知道日志程序将实行什么操作很关键。可能存在一个或多个实行其他操作的侦听程序。例如,您可能有一个向新用户发送的不雅观察者,欢迎新用户利用该系统。这种方法的代价在于 UserList 忽略所有依赖它的工具,它紧张关注在列表变动时掩护用户列表并发送这一事情。
此模式不限于内存中的工具。它是在较大的运用程序中利用的数据库驱动的查询系统的根本。
四、命令链模式命令链 模式以疏松耦合主题为根本,发送、命令和要求,或通过一组处理程序发送任意内容。每个处理程序都会自行判断自己能否处理要求。如果可以,该要求被处理,进程停滞。您可以为系统添加或移除处理程序,而不影响其他处理程序。清单 5 显示了此模式的一个示例。
清单 5. Chain.php
<?phpinterface ICommand{ function onCommand($name, $args);} class CommandChain{ private $_commands = array(); public function addCommand($cmd) { $this->_commands[] = $cmd; } public function runCommand($name, $args) { foreach ($this->_commands as $cmd ) { if ($cmd->onCommand($name, $args)) { return; } } }} class UserCommand implements ICommand{ public function onCommand($name, $args) { if ($name != 'addUser' ) { return false; } echo(\"大众UserCommand handling 'addUser'\公众); return true; }} class MailCommand implements ICommand{ public function onCommand($name, $args) { if ($name != 'mail') { return false; } echo(\"大众MailCommand handling 'mail'\"大众); return true; }} $cc = new CommandChain();$cc->addCommand(new UserCommand());$cc->addCommand(new MailCommand());$cc->runCommand('addUser', null);$cc->runCommand('mail', null);?>
此代码定义掩护 ICommand 工具列表的 CommandChain 类。两个类都可以实现 ICommand 接口 —— 一个对邮件的要求作出相应,另一个对添加用户作出相应。 图 5 给出了 UML。
图 5. 命令链及其干系命令
如果您运行包含某些测试代码的脚本,则会得到以下输出:
php chain.phpUserCommand handling 'addUser'MailCommand handling 'mail'
代码首先创建 CommandChain 工具,并为它添加两个命令工具的实例。然后运行两个命令以查看谁对这些命令作出了相应。如果命令的名称匹配 UserCommand 或 MailCommand,则代码失落败,不发生任何操作。
为处理要求而创建可扩展的架构时,命令链模式很有代价,利用它可以办理许多问题。
五、策略模式我们讲述的末了一个设计模式是策略 模式。在此模式中,算法是从繁芜类提取的,因而可以方便地更换。例如,如果要变动搜索引擎中排列页的方法,则策略模式是一个不错的选择。思考一下搜索引擎的几个部分 —— 一部分遍历页面,一部分对每页排列,另一部分基于排列的结果排序。在繁芜的示例中,这些部分都在同一个类中。通过利用策略模式,您可将排列部分放入另一个类中,以便变动页排列的办法,而不影响搜索引擎的别的代码。
作为一个较大略的示例,清单 6 显示了一个用户列表类,它供应了一个根据一组即插即用的策略查找一组用户的方法。
清单 6. Strategy.php
<?phpinterface IStrategy{ function filter($record);} class FindAfterStrategy implements IStrategy{ private $_name; public function __construct($name) { $this->_name = $name; } public function filter($record) { return strcmp($this->_name, $record) <= 0; }} class RandomStrategy implements IStrategy{ public function filter($record) { return rand(0, 1) >= 0.5; }} class UserList{ private $_list = array(); public function __construct($names) { if ($names != null) { foreach($names as $name) { $this->_list[]= $name; } } } public function add($name) { $this->_list[] = $name; } public function find($filter) { $recs = array(); foreach ($this->_list as $user) { if ($filter->filter($user)) { $recs []= $user; } } return $recs; }} $ul = new UserList(array(\"大众Andy\"大众, \公众Jack\"大众, \"大众Lori\"大众, \公众Megan\"大众));$f1 = $ul->find(new FindAfterStrategy(\公众J\"大众));print_r($f1); $f2 = $ul->find(new RandomStrategy());print_r($f2);?>
此代码的 UML 如图 6 所示。
图 6. 用户列表和用于选择用户的策略
UserList 类是打包名称数组的一个包装器。它实现 find 方法,该方法利用几个策略之一来选择这些名称的子集。这些策略由IStrategy 接口定义,该接口有两个实现:一个随机选择用户,另一个根据指定名称选择其后的所有名称。运行测试代码时,将得到以下输出:
php strategy.phpArray( [0] => Jack [1] => Lori [2] => Megan)Array( [0] => Andy [1] => Megan)
测试代码为两个策略运行同一用户列表,并显示结果。在第一种情形中,策略查找排列在 J 后的任何名称,以是您将得到 Jack、Lori 和 Megan。第二个策略随机选取名称,每次会产生不同的结果。在这种情形下,结果为 Andy 和 Megan。
策略模式非常适宜繁芜数据管理系统或数据处理系统,二者在数据筛选、搜索或处理的办法方面须要较高的灵巧性。
结束语本文先容的仅仅是 PHP 运用程序中利用的几种最常见的设计模式。在设计模式 一书中演示了更多的设计模式。不要因架构的神秘性而放弃。模式是一种绝妙的理念,适用于任何编程措辞、任何技能水平。