一些面向對象的編程方式,提供了一種構建對象間復雜網絡互連的能力。當對象們連接在一起時,它 們就可以相互提供服務和信息。
通常來說,當某個對象的狀態發生改變時,你仍然需要對象之間 能互相通信。但是出於各種原因,你也許並不願意因為代碼環境的改變而對代碼做大的修改。也許,你 只想根據你的具體應用環境而改進通信代碼。或者,你只想簡單的重新構造通信代碼來避免類和類之間 的相互依賴與相互從屬。
問題
當一個對象的狀態發生改變時,你如何通知其他對象?是 否需要一個動態方案――一個就像允許腳本的執行一樣,允許自由連接的方案?
解決方案
觀測模式允許一個對象關注其他對象的狀態,並且,觀測模式還為被觀測者提供了一種觀測結構 ,或者說是一個主體和一個客體。主體,也就是被觀測者,可以用來聯系所有的觀測它的觀測者。客體 ,也就是觀測者,用來接受主體狀態的改變
觀測就是一個可被觀測的類(也就是主題)與一個 或多個觀測它的類(也就是客體)的協作。不論什麼時候,當被觀測對象的狀態變化時,所有注冊過的 觀測者都會得到通知。
觀測模式將被觀測者(主體)從觀測者(客體)種分離出來。這樣,每個 觀測者都可以根據主體的變化分別采取各自的操作。(觀測模式和Publish/Subscribe模式一樣,也是一 種有效描述對象間相互作用的模式。)
觀測模式靈活而且功能強大。對於被觀測者來說,那些查 詢哪些類需要自己的狀態信息和每次使用那些狀態信息的額外資源開銷已經不存在了。另外,一個觀測 者可以在任何合適的時候進行注冊和取消注冊。你也可以定義多個具體的觀測類,以便在實際應用中執 行不同的操作。
實例代碼
舉例來說,你可以使用觀測模式為你的PHP腳本來創建一個更靈 活的記錄錯誤的句柄。因為,默認的錯誤記錄句柄也許只會在屏幕上顯示一些出錯信息,但是增強後的 句柄還可以將出錯信息寫進一個日志文件中,或將出錯信息寫進系統日志之中,或將出錯信息通過電子 郵件發送出去,或利用聲音報告出錯信息。你甚至還可以構造一種有級別的報錯方案,只允許向那些已 經為具體的出錯信息注冊過的觀測者報告。從一般的警告信息到像數據庫失靈之類的嚴重出錯信息都可 以報告。
下面,我們用觀測模式來為PHP創建一系列的類來實現剛才所說的那些功能。新建一個 名為ErrorHandler的類, 它就是觀測模式的主體,也就是被觀測者。再建另外兩個名為 FileErrorLogger和 EmailErrorLogger的類, 它們是觀測客體(即觀測者)。FileErrorLogger類將出 錯信息寫入日志文件,EmailErrorLogger類利用電子郵件發送出錯信息。在UML中,可以表示如下:
為了實 現以觀測模式為基礎的錯誤記錄句柄,首先我們注意到作為觀測者的FileErrorLogger類和 EmailErrorLogger類什麼也不能做。那麼,FileErrorLogger類是如何向一個文件寫出錯信息, EmailErrorLogger類又如何發送電子郵件的? 接下來,讓我來看看用來實現觀測模式的技術細節,然後 ,再集中精力來看看該模式的主體――ErrorHandler的細節。最後,再寫一些錯誤處理函數來調用這個 ErrorHandler類。
最後用下面的這一段代碼來表示:
// PHP4
$eh =& getErrorHandlerInstance();
$eh->attach(new EmailErrorLogger (‘[email protected]’));
$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() { /* ... */ }
}