>
我们通常认为避免使用全局变量是一种好的选择,因此,对象经常被作为参数从一段代码传递到另一段。但是传递实例的一个问题就是对象有时候不知道将要传递给谁——?经过一个函数后才被传递到真正需要这个对象的函数。
为了编写,阅读,修改代码的方便,最好能够减少不同对象的数量,并且能够将大量广泛使用的对象统一表示为一个单一,常用的对象。
问题:
你如何通过单一的全局的对象来获取对其它对象的引用?
解决方案:
“注册模式”就像“对象的电话簿”——储存并且能够取回对对象引用的登记簿。(注:PHP中的“联合数组”也起到了类似“电话簿”的功能。事实上,“注册模式”就是围绕PHP中强大的数组完成的。)“注册模式”的一些特性经常被包含在“单一模式”中(参见第四章),使得“注册模式”成为你整个应用信息的决定性来源。
注释:“注册模式”类主要参考了Martin Fowlerdescribes用java语言实现的Patterns of Enterprise Application Architecture(企业应用程序体系结构模型)。Marcus Baker谢了一篇详细的PHP中应用“注册模式”的文章。该文章可在PHPPatterns.com的站点获的
(http://www.PHPpatterns.com/index.PHP/article/articleview/75/1/1/)。Baker也涉及了一些测试considerations,示范了测试驱动的开发方法。
样本代码:
正如Martin Flower在他的“注册模式”一文中提及的样本代码所示,你可以用各种方法,提供各种接口实现“注册模式”。让我们仔细探究这种想法,并建立PHP4中的“注册模式”的一些不同实现。
让我们以编写能储存并恢复对象实例并能对“注册模式”提供全局访问的代码开始。这个类的实例变量能够缓存对象,并且“注册模式”本身是一个“单一模式”。像以前一样,测试决定需求。我们的第一个测试要确定“注册模式”是一个“单件模式”类。
// PHP4 class RegistryPHP4TestCase extends UnitTestCase { function testRegistryIsSingleton() { $this->assertIsA($reg =& Registry::getInstance(), ‘Registry’); $this->assertReference($reg, Registry::getInstance()); } } |
这里,要把你在以前几章“单件模式”中学到的知识用上,你应该能够很快写出能够通过该测试的类。以下是一个满足测试要求的“注册模式”类(ignoring the code required to enforce no direct object creation):
class Registry { function &getInstance() { static $instance = array(); if (!$instance) $instance[0] =& new Registry; return $instance[0]; } } |
一个简单的静态数组就足够记录这个单一实例了。
接下来,让我们转到“注册模式”独特的特性上面。一个“注册模式”应该提供get() 和set()方法来存储和取得对象(用一些属性key)而且也应该提供一个isValid()方法来确定一个给定的属性是否已经设置。
这三个方法的一个简单实现在接下来讨论。这里是两个isValid():方法的测试方法。
代码:
class RegistryPHP4TestCase extends UnitTestCase {function testRegistryIsSingleton() { /*...*/ } function testEmptyRegistryKeyIsInvalid() {$reg =& Registry::getInstance() $this->assertFalse($reg->isValid('key')); } function testEmptyRegistryKeyReturnsNull() {$reg =& Registry::getInstance(); $this->assertNull($reg->get('key')); } } |
作者注:assertFalse()
assertFalse()仅仅是assertTrue()的反面,如果第一个参数预期是PHP中的布尔值false,测试通过。
通过基于测试驱动的开发方式,你可以编写尽可能少的代码来符合你现阶段的测试需求,你也可以增加测试——如果你还未满足这个类的需求。
以下为满足前述测试要求的最简单的代码:
代码:
class Registry {function isValid() {return false;} function get() {} function &getInstance() {static $instance = array(); if (!$instance) $instance[0] =& new Registry; return $instance[0]; } } |
确实,isValid() 和 get()方法的代码片断并不是非常好,但是所有的测试通过了!下面我们添加更丰富的测试用例。
代码:
class RegistryPHP4TestCase extends UnitTestCase {function testRegistryIsSingleton() { /*...*/ } function testEmptyRegistryKeyIsInvalid() { /*...*/ } function testEmptyRegistryKeyReturnsNull() { /*...*/ } function testSetRegistryKeyBecomesValid() {$reg =& Registry::getInstance(); $test_value = 'something';$reg->set('key', $test_value); $this->assertTrue($reg->isValid('key')); } } |
为了满足testSetRegistryKeyBecomesValid()方法,“注册模式”类必须要有追踪(tracking)的功能——如果特定的属性用set()方法设置了。 很明显的一种实现方式是利用PHP4中的联合数组作为实例变量,并利用PHP的array_key_exists()函数来检测我们想要的索引是否被创建了。
下面是“注册模式类”更进一步的实现。
代码:
class Registry {var $_store = array(); function isValid($key) {return array_key_exists($key, $this->_store);} function set($key, $obj) {$this->_store[$key] = $obj; function get() {} function &getInstance() {static $instance = array() if (!$instance) $instance[0] =& new Registry; return $instance[0]; } } |
通过在声明时初始化$_store变量,就没有设置构造函数的必要了。(注:在PHP4中没有适当的访问控制标记,以下代码遵循私有变量以下划线作前缀的约定)