php设计模式介绍之观测模式2009-12-30wangyun522一些面向对象的编程方式,提供了一种构建对象间复杂网络互连的能力。当对象们连接在一起时,它 们就可以相互提供服务和信息。通常来说,当某个对象的状态发生改变时,你仍然需要对象之间 能互相通信。但是出于各种原因,你也许并不愿意因为代码环境的改变而对代码做大的修改。也许,你 只想根据你的具体应用环境而改进通信代码。或者,你只想简单的重新构造通信代码来避免类和类之间 的相互依赖与相互从属。问题当一个对象的状态发生改变时,你如何通知其他对象?是 否需要一个动态方案――一个就像允许脚本的执行一样,允许自由连接的方案?解决方案观测模式允许一个对象关注其他对象的状态,并且,观测模式还为被观测者提供了一种观测结构 ,或者说是一个主体和一个客体。主体,也就是被观测者,可以用来联系所有的观测它的观测者。客体 ,也就是观测者,用来接受主体状态的改变观测就是一个可被观测的类(也就是主题)与一个 或多个观测它的类(也就是客体)的协作。不论什么时候,当被观测对象的状态变化时,所有注册过的 观测者都会得到通知。观测模式将被观测者(主体)从观测者(客体)种分离出来。这样,每个 观测者都可以根据主体的变化分别采取各自的操作。(观测模式和Publish/Subscribe模式一样,也是一 种有效描述对象间相互作用的模式。)观测模式灵活而且功能强大。对于被观测者来说,那些查 询哪些类需要自己的状态信息和每次使用那些状态信息的额外资源开销已经不存在了。另外,一个观测 者可以在任何合适的时候进行注册和取消注册。你也可以定义多个具体的观测类,以便在实际应用中执 行不同的操作。实例代码举例来说,你可以使用观测模式为你的PHP脚本来创建一个更灵 活的记录错误的句柄。因为,默认的错误记录句柄也许只会在屏幕上显示一些出错信息,但是增强后的 句柄还可以将出错信息写进一个日志文件中,或将出错信息写进系统日志之中,或将出错信息通过电子 邮件发送出去,或利用声音报告出错信息。你甚至还可以构造一种有级别的报错方案,只允许向那些已 经为具体的出错信息注册过的观测者报告。从一般的警告信息到像数据库失灵之类的严重出错信息都可 以报告。下面,我们用观测模式来为PHP创建一系列的类来实现刚才所说的那些功能。新建一个 名为ErrorHandler的类, 它就是观测模式的主体,也就是被观测者。再建另外两个名为 FileErrorLogger和 EmailErrorLogger的类, 它们是观测客体(即观测者)。FileErrorLogger类将出 错信息写入日志文件,EmailErrorLogger类利用电子邮件发送出错信息。在UML中,可以表示如下:

为了实 现以观测模式为基础的错误记录句柄,首先我们注意到作为观测者的FileErrorLogger类和 EmailErrorLogger类什么也不能做。那么,FileErrorLogger类是如何向一个文件写出错信息, EmailErrorLogger类又如何发送电子邮件的? 接下来,让我来看看用来实现观测模式的技术细节,然后 ,再集中精力来看看该模式的主体――ErrorHandler的细节。最后,再写一些错误处理函数来调用这个 ErrorHandler类。最后用下面的这一段代码来表示:// PHP4
$eh =& getErrorHandlerInstance();
$eh->attach(new EmailErrorLogger (‘jsweat_php@yahoo.com’));
$eh->attach(new FileErrorLogger(fopen (‘error.log’,’w’)));
set_error_handler (‘observer_error_handler’);
// ... later
trigger_error(‘this is an error’);ErrorHandler类是一种单件模式(参考第4章:The Singleton Pattern)。它可以通过函数Attach()来注册各种错误信息观测者,而set_error_handler() 函数就是一个指向ErrorHandler类的函数。最后,当一个错误信息被触发后,所有的观测者都会得到通 知。为了使这次观测的操作生效,你的测试必须能证明所有的这些操作(将错误信息写入日志, 利用电子邮件发送错误信息)都能得到执行,并且能正常工作。简而言之,让我们来看看一系列简单的 测试。(和这个实例有关的其他更多实例,可以在本书附带的源代码中找到)这里有 FileErrorLogger类联合测试的一部分代码:它用来测试当FileErrorlogger类被某个对象实例化时,是 否具有向一个文件写日志的能力。
class FileErrorLoggerTestCase extends UnitTestCase {
var $_fh;
var $_test_file = ‘test.log’;
function setup() {
@unlink($this->_test_file);
$this->_fh = fopen ($this->_test_file, ‘w’);
}
function TestRequiresFileHandleToInstantiate() { /* ... */ }
function TestWrite() {
$content = ‘test’.rand(10,100);
$log =& new FileErrorLogger ($this->_fh);
$log->write($content);
$file_contents = file_get_contents ($this->_test_file);
$this->assertWantedPattern (‘/’.$content.’$/’, $file_contents);
}
function TestWriteIsTimeStamped() { /* ... */ }
}