统计在线人数...

《PHP设计模式介绍》第九章 观测模式

[ 来源:PHPchina | 作者:佚名 | 时间:2008-6-14 下午 01:52:14 | 浏览:统计中... ]

一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力。当对象们连接在一起时,它们就可以相互提供服务和信息。

通常来说,当某个对象的状态发生改变时,你仍然需要对象之间能互相通信。但是出于各种原因,你也许并不愿意因为代码环境的改变而对代码做大的修改。也许,你只想根据你的具体应用环境而改进通信代码。或者,你只想简单的重新构造通信代码来避免类和类之间的相互依赖与相互从属。

问题

当一个对象的状态发生改变时,你如何通知其他对象?是否需要一个动态方案――一个就像允许脚本的执行一样,允许自由连接的方案?

解决方案

观测模式允许一个对象关注其他对象的状态,并且,观测模式还为被观测者提供了一种观测结构,或者说是一个主体和一个客体。主体,也就是被观测者,可以用来联系所有的观测它的观测者。客体,也就是观测者,用来接受主体状态的改变

观测就是一个可被观测的类(也就是主题)与一个或多个观测它的类(也就是客体)的协作。不论什么时候,当被观测对象的状态变化时,所有注册过的观测者都会得到通知。

观测模式将被观测者(主体)从观测者(客体)种分离出来。这样,每个观测者都可以根据主体的变化分别采取各自的操作。(观测模式和Publish/Subscribe模式一样,也是一种有效描述对象间相互作用的模式。)

观测模式灵活而且功能强大。对于被观测者来说,那些查询哪些类需要自己的状态信息和每次使用那些状态信息的额外资源开销已经不存在了。另外,一个观测者可以在任何合适的时候进行注册和取消注册。你也可以定义多个具体的观测类,以便在实际应用中执行不同的操作。

实例代码

举例来说,你可以使用观测模式为你的PHP脚本来创建一个更灵活的记录错误的句柄。因为,默认的错误记录句柄也许只会在屏幕上显示一些出错信息,但是增强后的句柄还可以将出错信息写进一个日志文件中,或将出错信息写进系统日志之中,或将出错信息通过电子邮件发送出去,或利用声音报告出错信息。你甚至还可以构造一种有级别的报错方案,只允许向那些已经为具体的出错信息注册过的观测者报告。从一般的警告信息到像数据库失灵之类的严重出错信息都可以报告。

下面,我们用观测模式来为PHP创建一系列的类来实现刚才所说的那些功能。新建一个名为ErrorHandler的类,它就是观测模式的主体,也就是被观测者。再建另外两个名为FileErrorLogger和EmailErrorLogger的类,它们是观测客体(即观测者)。FileErrorLogger类将出错信息写入日志文件,EmailErrorLogger类利用电子邮件发送出错信息。在UML中,可以表示如下:

 

为了实现以观测模式为基础的错误记录句柄,首先我们注意到作为观测者的FileErrorLogger类和EmailErrorLogger类什么也不能做。那么,FileErrorLogger类是如何向一个文件写出错信息,EmailErrorLogger类又如何发送电子邮件的?接下来,让我来看看用来实现观测模式的技术细节,然后,再集中精力来看看该模式的主体――ErrorHandler的细节。最后,再写一些错误处理函数来调用这个ErrorHandler类。

最后用下面的这一段代码来表示:

//PHP4
$eh=&getErrorHandlerInstance();
$eh->attach(newEmailErrorLogger(
‘jsweat_php@yahoo.com’));
$eh->attach(newFileErrorLogger(fopen(‘error.log’,’w’)));
set_error_handler(‘observer_error_handler’);
//...later
trigger_error(‘thisisanerror’);

ErrorHandler类是一种单件模式(参考第4章:TheSingletonPattern)。它可以通过函数Attach()来注册各种错误信息观测者,而set_error_handler()函数就是一个指向ErrorHandler类的函数。最后,当一个错误信息被触发后,所有的观测者都会得到通知。

为了使这次观测的操作生效,你的测试必须能证明所有的这些操作(将错误信息写入日志,利用电子邮件发送错误信息)都能得到执行,并且能正常工作。简而言之,让我们来看看一系列简单的测试。(和这个实例有关的其他更多实例,可以在本书附带的源代码中找到)

这里有FileErrorLogger类联合测试的一部分代码:它用来测试当FileErrorlogger类被某个对象实例化时,是否具有向一个文件写日志的能力。

classFileErrorLoggerTestCaseextendsUnitTestCase{
var$_fh;
var$_test_file=‘test.log’;
functionsetup(){
@unlink($this->_test_file);
$this->_fh=fopen($this->_test_file,‘w’);
}
functionTestRequiresFileHandleToInstantiate(){/*...*/}
functionTestWrite(){
$content=‘test’.rand(10,100);
$log=&newFileErrorLogger($this->_fh);
$log->write($content);
$file_contents=file_get_contents($this->_test_file);
$this->assertWantedPattern(‘/’.$content.’$/’,$file_contents);
}
functionTestWriteIsTimeStamped(){/*...*/}
}

在这个测试中,setup()函数创建了一个文件指针,指向一个名为“test.log”的新文件。并且,将该指针保存在变量$_fh中,这个可写的文件指针将作为一个变量传递给FileErrorlogger对象的实例,进行测试。变量$content的值将传递给函数write(),并且,在存储结束后,还将用来被检查$content的值是否确实被正确写入test.log文件中。

(这个测试要求PHP必须具有向那个新建的test.log中写数据的权限。)

下面的一些代码也许可以帮助FileErrorLogger类通过测试。

classFileErrorLogger{
var$_fh;
functionFileErrorLogger($file_handle){
$this->_fh=$file_handle;
}
functionwrite($msg){
fwrite($this->_fh,date(‘Y-m-dH:i:s:‘).$msg);
}
}

一个类似的测试代码可以使EmailErrorLogger类生效。

classEmailErrorLoggerTestCaseextendsUnitTestCase{
functionTestEmailAddressFirstConstructorParameter(){
$log=&newEmailErrorLogger;
$this->assertErrorPattern(‘/missing.*1/i’);
}
functionTestMail(){
$log=&newEmailErrorLogger(
‘jsweat_php@yahoo.com’);
$log->mail(‘testmessage’);
}
}

接下来,通过这个测试的EmailErrorLogger类的代码如下:

classEmailErrorLogger{
var$_addr;
var$_subject;
functionEmailErrorLogger($addr,
$subject=’ApplicationErrorMessage’){
$this->_addr=$addr;
$this->_subject=$subject;
}
functionmail($msg){
mail($this->_addr
,$this->_subject
,date(‘Y-m-dH:i:s:‘).$msg);
}
}

[1] [2] [3]  下一页

共有0人参与评价,平均得分:0分
评论内容只代表网友观点,与本站立场无关! 查看完整内容
   

当前在线人数
QQ:748838 MSN:allen_xia#msn.com E-mail:allenxia666#126.com QQ群:站长联盟北方区-北京(28200145) 站长联盟南方区-上海(67713522)