5->The Registry Pattern(HRTSEA预定)
时间:2006-03-27
来源:互联网
The Registry
Pattern
Because it’s generally considered “good form” to avoid the use of global variables, objects are
usually passed from one code segment to another as parameters. But the problem with passing
instances is that objects sometimes end up as “tramp data,” passed into one function only
to be passed again to another function which truly needs the object.
To make writing, reading, and consuming code simpler, it’s best to minimize the number of different
objects and consolidate knowledge of how to get to a myriad of other widely-used objects into
a single, well-known object.
The Problem
How can you get references to objects through a single, well-known, object?
The Solution
The Registry design pattern is like an “object phone book”―a directory―that stores and retrieves
作者: PHPChina 发布时间: 2006-03-26
姓名:邢海平
QQ:270479602
手机:13476189083(手机短信)
[ 本帖最后由 HRTSEA 于 2006-3-27 16:12 编辑 ]
作者: HRTSEA 发布时间: 2006-03-27
作者: PHPChina 发布时间: 2006-03-28
作者: Bantu 发布时间: 2006-03-28
为了编写,阅读,修改代码的方便,最好能够减少不同对象的数量,并且能够将大量广泛使用的对象统一表示为一个单一,常用的对象。
问题:
你如何通过单一的“well known”对象获的对对象的引用?
解决方案:
“注册模式”(registry pattern)。“注册模式”就像“对象的电话簿”――储存并且能够取回对对象引用的登记簿。(注: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中没有适当的可见性修饰府,以下代码遵循私有变量以下划线作前缀的约定)
测试又通过了!现在我们想最终特性进发:给定一个属性key,“注册模式”类的get()方法将返回一个对特定对象的引用。一下为符合这一要求的测试用例。
代码:
class RegistryPHP4TestCase extends UnitTestCase
{function testRegistryIsSingleton() { /*...*/ }
function testEmptyRegistryKeyIsInvalid() { /*...*/ }
function testEmptyRegistryKeyReturnsNull() { /*...*/ }
function testSetRegistryKeyBecomesValid() { /*...*/ }
function testSetRegistryValueIsReference()
{$reg =& Registry::getInstance();$test_value = 'something';
$reg->set('key', $test_value);
$this->assertReference($test_value, $reg->get('key'));
//another way to test the reference
$test_value .= ' else';
$this->assertEquual('something else',$reg->get('key'));
}
}
以下为“注册模式”类的完整实现代码。
代码:
class Registry
{var $_store = array();
function isValid($key)
{return array_key_exists($key, $this->_store);}
function &get($key)
{if (array_key_exists($key, $this->_store))
return $this->_store[$key];}
function set($key, &$obj)
{$this->_store[$key] =& $obj;}
function &getInstance()
{static $instance = array();
if (!$instance) $instance[0] =& new Registry;
return $instance[0];
}
}
“注册模式”的get()方法会返回一个对象引用。类似的,set()方法的$obj参数要求得到一个对象引用并被赋值$this->_store[$key].。get()和set()方法的联合恰当使用能够满足assertReference()测试。
作者注:
“注册模式”的get()Registry::get()方法的代码应该写成@$this->_store[$key;]的形式,但是最好避免使用错误抑制符,而且使用错误抑制符的代码会变的摸棱两可,你需要花费额外的时间去了解你是否会再次访问这段代码。array_key_exists()方法指出了应该避免的错误。
php5中,对象句柄(引用)带来了革命性的变化――你可以从对象引用的困境中解脱出来。事实上php5中“注册模式”的实现变的简单多了。因为你可以在不用担心因为没有通过引用传递对象而引起致命错误的情况下使用联合数组。在php5中,你甚至能在“注册模式”中混和使用对象和literals 。
一个例子:
在实际应用中“注册模式”会是什么样子?在网络应用程序开发中,通常我们只拥有一个数据库连接。(因此,广泛使用“单一模式”管理数据连接)但是,比如,由于历史遗留原因:你的应用的客户数据库与你的在线订单数据库是分开的,你的数据分析系统又把你的旧订单转移到一个存档数据库中,而且它与你的客户数据库及订单(现有,最近)数据库也是完全隔离的。那么,你怎么才能容易的管理三个数据库连接而不用创建三个单独的“单一模式”呢?答安就是使用“注册模式”。
代码:class DbConnections extends Registry {}
注:当你在你的代码中引入设计模式时,你的类名应该仍能反映他的角色和功能而没有必要使用模式的名字。使用模式的名字注释代码对与你的项目以外的程序员交流非常有帮助。但是在你的项目内,类的名字应该适合项目本身而且能够被项目成员很好的理解。虽然本章范例中的类名反映了设计模式的名字以及特定的实现方式,单这并不是必须的。这仅仅是为了例子的清晰明了而不是好的命名规范。
DbConnections类是一个“单态模式”类,又继承了“注册模式”――DbConnections综合了两者的优点。
以下的代码片断创建并在“注册模式”类中存储了对每一个数据库的连接。
代码:
//initial setup, somewhere near the start of your script
$dbc =& DbConnections::getInstance();
$dbc->set(
'contacts',
new MysqlConnection('user1', 'pass1', 'db1', 'host1'));
$dbc->set(
'orders',
new MysqlConnection('user2', 'pass2', 'db2', 'host2'));
$dbc->set(
'archives',
new MysqlConnection('user3', 'pass3', 'db3', 'host3'));
在其他类中将“注册模式”类连同数据一起载入就可以使用不同的连接了。
代码:
// domain model classes
class Customer {
var $db;
function Customer() {
$dbc =& DbConnections::getInstance();
$this->db =& $dbc->get('contacts');
}
//...
}
class Orders {
var $db_cur;
var $db_hist;
function Contact() {
$dbc =& DbConnections::getInstance();
$this->db_cur =& $dbc->get('orders');
$this->db_hist =& $dbc->get('archive');
}
//...
}
[ 本帖最后由 HRTSEA 于 2006-3-31 13:51 编辑 ]
作者: HRTSEA 发布时间: 2006-03-31
将注册模式实现为“单太模式”:
如前所述,把“注册模式”实现为“单件模式”有很多实现方式。
第一步,将“注册模式”实现为单态对象,(作者注:我们在第四章――The Singleton Pattern末尾简单讨论过)。
按照这种设计,注册模式类的任何一个实例都将访问同一个数组。我们把这个新类叫做RegistryGlobal以区别于我们前面开发的类,并反映这种实现方式的特性。
以下为反映这种思想的测试用例(他应该看起来很熟悉)。
代码:
class RegistryGlobalPHP4TestCase extends UnitTestCase {
function testRegistryGlobal() {
$reg =& new RegistryGlobal;
$this->assertFalse($reg->isValid('key'));
$this->assertNull($reg->get('key'));
$test_value = 'something';
$reg->set('key', $test_value);
$this->assertReference($test_value, $reg->get('key'));
}
}
实现代码如下所示:
class RegistryGlobal {
var $_store = array();
function isValid($key) {
return array_key_exists($key, $this->_store);
}
function &get($key) {
if (array_key_exists($key, $this->_store))
return $this->_store[$key];
}
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
isValid(), get(),和set()方法与我们前面开发的“注册模式”类完全相同。
下一步:我们来编写验证RegistryGlobal类是“单件模式”的测试用例。
代码:
class RegistryGlobalPHP4TestCase extends UnitTestCase {
function testRegistryGlobal() { /*...*/ }
function testRegistryGlobalIsMonoState() {
$reg =& new RegistryGlobal;
$reg2 =& new RegistryGlobal;
$this->assertCopy($reg, $reg2);
$test_value = 'something';
$reg->set('test', $test_value);
$this->assertReference(
$reg->get('test')
,$reg2->get('test'));
}
}
这里测试用例创建了RegistryGlobal类的两个实例,以确认他们不是对同一对象的引用――在一个实例内设置一个属性值(value),最后证实两个实例返回相同的对象。若测试通过RegistryGlobal类就拥有“单件模式”的行为。
代码:
define('REGISTRY_GLOBAL_STORE', '__registry_global_store_key__');
class RegistryGlobal
{var $_store;
function RegistryGlobal()
{if
(!array_key_exists(REGISTRY_GLOBAL_STORE, $GLOBALS)||!is_array($GLOBALS[REGISTRY_GLOBAL_STORE])) {$GLOBALS[REGISTRY_GLOBAL_STORE] = array();
}
$this->_store =& $GLOBALS[REGISTRY_GLOBAL_STORE];
}
function isValid($key)
{return array_key_exists($key, $this->_store);}
function &get($key)
{if (array_key_exists($key, $this->_store)) return $this->_store[$key];}
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
本替代方法中的神奇之处在于$this->_store =& $GLOBALS[REGISTRY_GLOBAL_STORE;] 这一行,引用操作符将全局数组绑定到实例变量$_store上。这是单态模式实现的关键所在:每次在对象中使用$this->_store变量时,作用反映到全局变量中。
但是并不推荐基于全局变量的解决方案。静态类变量会是更好的解决方案,如果php4支持这一特性的话。然而,我们可以在代码中通过引用实现静态类变量吗?
测试与 RegistryGlobal 类的测试相似。
//代码//
class RegistryMonoStatePHP4TestCase extends UnitTestCase {
function testRegistryMonoState() {
$this->assertCopy(
$reg =& new RegistryMonoState
,$reg2 =& new RegistryMonoState);
$this->assertFalse($reg->isValid(‘key’));
$this->assertNull($reg->get(‘key’));
$test_value = ‘something’;
$reg->set(‘key’, $test_value);
$this->assertReference($reg->get(‘key’), $reg2->get(‘key’));
}
}
要自己实现类静态变量,可以将一个对函数静态变量的引用绑定到类的实例变量上。
//代码//
class RegistryMonoState {var $_store;
function &_initRegistry() { static $store = array(); return $store;
}
function RegistryMonoState() {
$this->_store =& $this->_initRegistry();
}
function isValid($key) {
return array_key_exists($key, $this->_store);
}
function &get($key) {
if (array_key_exists($key, $this->_store))
return $this->_store[$key];
}
function set($key, &$obj) {
$this->_store[$key] =& $obj;
}
}
initRegistry()方法包含一个初始化为数组的静态变量。这个静态变量通过引用返回。在构造函数中$_store实例变量被赋与通过initRegistry()函数返回的引用――即静态数组。好!一个php4的类静态变量产生了。
使用类静态变量的实现:
php5中,没有必要自己实现类静态变量,因为php5直接支持类静态变量。因此,php5简化了实现。而且,php5中引用,对象不在有php4中的意义,但是assertReference() 处理了这种差别,如果两个变量指向同一个个对象句柄也可以通过测试。
以下是为php5改写的类似的Registry测试用例。
//代码//
// PHP5
class RegistryMonoStatePHP5TestCase extends UnitTestCase {
function testRegistryMonoState() {
$this->assertCopy(
$reg = new RegistryMonoState
,$reg2 = new RegistryMonoState);
$this->assertFalse($reg->isValid(‘key’));
$this->assertNull($reg->get(‘key’));
$test_value = new TestObj;
$reg->set(‘key’, $test_value);
$this->assertReference($test_value, $reg2->get(‘key’));
}
}
以下是php5版本的使用静态类变量的Registry类。
//代码//
class RegistryMonoState {
protected static $store = array();
function isValid($key) {
return array_key_exists($key, RegistryMonoState::$store);
}
function get($key) {
if (array_key_exists($key, RegistryMonoState::$store))
return RegistryMonoState::$store[$key];
}
function set($key, $obj) {
RegistryMonoState::$store[$key] = $obj;
}
}
php5中用这种方式编码Registry类的一个有趣的负效应是你可以以相同的代码使用实例或者静态方法。以下是证明仅仅使用静态方法的测试用例。
//代码//
class RegistryMonoStatePHP5TestCase extends UnitTestCase {
function testRegistryMonoState() { /*...*/ }
function testRegistryMonoStateStaticCalls() {
$this->assertFalse(RegistryMonoState::isValid(‘key’));
$this->assertNull(RegistryMonoState::get(‘key’));
$test_value = new TestObj; RegistryMonoState::set(‘key’, $test_value);
$this->assertIdentical($test_value, RegistryMonoState::get(‘key’));
}
现在你已经看到在php5中的静态调用接口,下面让我们在php4中实现相同的接口。在前面的php4“静态类变量”部分,实现需要使用“函数静态变量返回引用”来跟踪。Php4版本的静态调用接口测试与php5版本的测试类似。
//代码//
// PHP4
class RegistryStaticPHP4TestCase extends UnitTestCase {
function testRegistryStatic() {
$this->assertFalse(RegistryStatic::isValid(‘key’));
$this->assertNull(RegistryStatic::get(‘key’));
$test_value = ‘something’; RegistryStatic::set(‘key’, $test_value);
$this->assertReference($test_value, RegistryStatic::get(‘key’));
}
}
以下是符合测试要求的实现。
//代码//
class RegistryStatic {
function &_getRegistry() { static $store = array(); return $store;
}
function isValid($key) {
$store =& RegistryStatic::_getRegistry();
return array_key_exists($key, $store);
}
function &get($key) {
$store =& RegistryStatic::_getRegistry();
if (array_key_exists($key, $store))
return $store[$key];
}
function set($key, &$obj) {
$store =& RegistryStatic::_getRegistry();
$store[$key] =& $obj;
}
}
这个实现方法的重点是getRegistry()方法返回一个对静态数组的引用。
$store =& RegistryStatic::_getRegistry();这一行,在随后的函数中把变量$store通过引用赋给静态数组,允许所有的函数可以静态访问数组,允许所有的方法可以被静态调用。
也可以不使用php4“静态类变量跟踪”达到相同的效果:将原先的基于“单态模式”的Registry类
与一个包装类结合以达到允许静态调用。这个类与testRegistryStatic()有相同的测试,但是他的实现如下所示:
//代码//
class RegistryStatic {
function isValid($key) {
$reg =& Registry::getInstance();
return $reg->isValid($key);
}
function &get($key) {
$reg =& Registry::getInstance();
return $reg->get($key);
}
function set($key, &$obj) {
$reg =& Registry::getInstance();
$reg->set($key, $obj);
}
}
结论:
虽然Registry“注册模式”简化了对大量对象的访问,但是仍然有许多问题――与全局变量联合。你需要确定要求的属性Key在访问之已经被初始化了,而且设置属性的方法可以全局访问,你的对象仍然可能在你的代码的其他部分出乎意料的被替换掉。显然,全局数据非常有好处,方便,但是你需要时刻记住任何全局数据都是有一些不可信的。
Embedded Registry
内含的Registry模式
除了单独使用Registry模式――如本章所示,Registry模式非常强大当与其他对象结合时。例如:当对象的创建代价非常昂贵(例如需要查询大量数据库来初始化对象)时,而且对象在这个应用中被使用一次或多次,如果这样,你能创建一个结合了Factory模式 (见第三章) 和 Registry 模式 的“Finder”类以获得已经创建的对象的缓存而不用再次创建他们?
以下是一个Contact类,AddressBook类是工厂类。
//代码//
class AddressBook {
function &findById($id) {
return new Contact($id);
}
}
class Contact {
function Contact($id) {
// expensive queries to create object using $id
}
// ... other methods
}
你可以在AddressBook类中插入Registry模式来提供缓存。代码可以如下所示:
class AddressBook {
var $registry;
function AddressBook() {
$this->registry =& Registry::getInstance();
}
function &findById($id) {
if (!$this->registry->isValid($id)) {
$this->registry->set($id, new Contact($id));
}
return $this->registry->get($id);
}
}
AddressBook类的构造函数将registry绑定到一个实例变量。当创建了一个特定的ID并被findById()方法调用时,Registry被检查以确定对象是否已经被缓存。如果没有,将创建一个新的对象并存储在Registry中。被调用的对象将通过函数从Registry中取回并被返回。
作者: HRTSEA 发布时间: 2006-03-31
作者: HRTSEA 发布时间: 2006-03-31
作者: PHPChina 发布时间: 2006-04-04
作者: feifengxlq 发布时间: 2006-04-04
作者: HRTSEA 发布时间: 2006-04-09
如果翻译的顺利,PHPchina将和出版社探讨出版此书籍,翻译的人员将会拥有中文的版权,PHPchina保护大家的权益,如有任何精神或物质的收益,也隶属各位的支持者。如果有哪位和出版社有密切关系的,请和PHPchina联系。
同时为了支持大家翻译,每人一件T-shirt,请大家填写,顶贴领取:
注册帐号:
地址:
邮编:
收件人:
电话:
尺码:
颜色:
件数:
作者: forest 发布时间: 2006-04-09
作者: forest 发布时间: 2006-04-09
热门阅读
-
office 2019专业增强版最新2021版激活秘钥/序列号/激活码推荐 附激活工具
阅读:74
-
如何安装mysql8.0
阅读:31
-
Word快速设置标题样式步骤详解
阅读:28
-
20+道必知必会的Vue面试题(附答案解析)
阅读:37
-
HTML如何制作表单
阅读:22
-
百词斩可以改天数吗?当然可以,4个步骤轻松修改天数!
阅读:31
-
ET文件格式和XLS格式文件之间如何转化?
阅读:24
-
react和vue的区别及优缺点是什么
阅读:121
-
支付宝人脸识别如何关闭?
阅读:21
-
腾讯微云怎么修改照片或视频备份路径?
阅读:28