《PHP設計模式介紹》第五章 注冊模式(2)_PHP教程
推薦:《PHP設計模式介紹》第四章 單件模式幾乎所有面向對象的程序中,總有一兩個資源被創建出來,在程序應用中持續被共享使用。例如,這樣的一個資源,在一個電子商務程序的數據庫連接中使用:這個連接在應用程序啟動時初始化,程序于是
測試又通過了!現在我們想最終特性進發:給定一個屬性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()測試。
作者注:
“注冊模式”的getRegistry::get()方法的代碼應該寫成@$this->_store[$key;]的形式,但是最好避免使用錯誤抑制符,使用錯誤抑制符的代碼會變的摸棱兩可,你需要花費額外的時間去了解你是否會再次訪問這段代碼。array_key_exists()方法指出了應該避免的錯誤。
PHP5中,對象句柄(引用)帶來了革命性的變化——你可以從對象引用的困境中解脫出來。事實上PHP5中注冊模式的實現變的簡單多了。因為你再也不用擔心因為沒有通過引用傳遞對象而引起致命錯誤的情況下使用聯合數組。在PHP5中,你甚至能在注冊模式中混和使用對象和變量。
一個例子:
在實際應用中“注冊模式”會是什么樣子?在網絡應用程序開發中,通常我們只擁有一個數據庫連接。(因此,廣泛使用“單一模式”管理數據連接)但是,比如,由于歷史遺留原因:你的應用的客戶數據庫與你的在線訂單數據庫是分開的,你的DBA又把你的舊訂單轉移到一個存檔數據庫中,而且它與你的客戶數據庫及訂單(現有,最近)數據庫也是完全隔離的。那么,你怎么才能方便地管理三個數據庫連接而不用創建三個單獨的“單一模式”呢?答安就是使用“注冊模式”。
代碼:
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'); } //... } |
一個類依據客戶數據庫建模,另一個類依據歷史和現在的客戶訂單建模。取得正確的數據庫鏈接需要兩個步驟:找到注冊模式類,從中找出與給定的屬性(key)相匹配的對象。
將注冊模式實現為單件模式:
如前所述,把注冊模式實現為單件模式有很多實現方式。
第一步,將注冊模式實現為單件對象,(作者注:我們在第四章——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; } } |
分享:《PHP設計模式介紹》第三章 工廠模式在面向對象編程中, 最通常的方法是一個new操作符產生一個對象實例,new操作符就是用來構造對象實例的。但是在一些情況下, new操作符直接生成對象會帶來一些問題。舉例來說, 許多類型對象的創造需
- 相關鏈接:
- 教程說明:
PHP教程-《PHP設計模式介紹》第五章 注冊模式(2)。