一、單件模式
英文叫做sington。其他語言中有叫做單例模式,其實都是一樣的道理。保證只會出現單個實例,所以是單例。翻譯成單件,永遠只會產生一件,呵呵。
還有翻譯成單元素模式。其實關鍵是看這個英文比較好。英文是sington,統一是使用這個單詞。
單件模式的目的我理解如下:
避免重復創建(實例化)對象,已經有現成的實例就用現成的。
減少資源的浪費(因為創建多個實例,浪費內存,完全沒必要),單件模式保證了每時每刻引用的都是同一個實例。
為什么同時創建多個實例會引起邏輯上的錯誤呢?
$obj1
$obj2
多個實例。可能會覆蓋掉里面的靜態static變量嗎? 不是這樣子的。
其實是因為我目前還沒遇到更加嚴重的問題。目前是簡單的應用。
二、我覺得單件模式實踐的注意點在下面幾個方面
1、不要使用全局變量來保存實例值。因為全局變量在任何地方都可以被訪問和修改,這就意味着可能會被其他代碼給破壞掉值,這樣就達不到永遠是同一
個實例的效果。
2、使用static靜態變量。這樣只能函數內部訪問。解決了全局變量被破壞的風險。
我覺得這是很多要做到實例唯一的一個關鍵部分。像框架中為保證所有對類實例的引用是唯一一個,都是將實例保存在static變量中。這樣子下回調用的
時候也是同一個實例。不會重復創建。
抓住了這個精髓,我覺得是可以變化的。並不一定要遵循設計模式書中的做法。因為目標是相同的。技巧可以不同。
3、一般將類的__construct()構造函數標識為private,這樣就是避免程序員直接實例化這個類。根據每種語言的特點,加上private關鍵詞,程序員new一
個對象,就會報錯。這種技巧是一種輔助手段。為保證只有一個類實例做輔助方案的。核心還是在於第二點的static關鍵字。
只要程序員約定好,這個輔助手段其實可以沒有仍然能夠做到單件。不是為設計模式而設計。了解實現目標才是關鍵的。
我在想,可以使用protected來替代嗎?
目標就是,要禁止使用new來實例化這個函數。當實例化一個類的時候,默認會去執行構造函數,而加上protected和private關鍵字的成員,
都同樣不能在類外部調用的。所以使用protected也是可行的。
但為什么要使用private呢?還有個好處,可以避免被繼承的子類所重寫,覆蓋掉方法的內容。因為加上protected標識的成員是能夠被子類給重寫的。
既然對構造函數加上了private,那就意味着子類是不能繼承這個類的。了解這個特性設計的時候就要考慮,無子類繼承它的概念。
4、代碼實踐
class test
{
static $_instance = false;
private function __construct()
{
/*一般將構造函數加上private關鍵字,這樣子避免直接使用外部直接new來實例對象,當然內部使用new來創建是不會影響的*/
}
function get_instance()
{
if(self::$_instance==false && !is_object(self::$_instance)){
self::$_instance = new test();
}
return self::$_instance;
}
}
實際項目開發中,有個變體是,創建a、b、c的實例都需要通過一個公共的方法來調用,這樣子可以實現單件模式。
類似於thinkphp等框架中的。
像下面是phpcms中的
pc_base::load_app_calss('test');
load_app_class($class_name)
{
static $class_array = array();
if(isset($class_array[$class_name]) && is_object($class_array[$class_name])) )
return $class_array[$class_name];
}else{
//這里可能還要有代碼載入這個類文件,根據實際而定。可以是去默認一個文件夾夾中載入。也可以認為調用這個方法的前提是類文件要載入進來
$class_array[$class_name] = new $class_array[$class_name];
return $class_array[$class_name];
}
其實可以避免創建很多數據庫鏈接。寫到這里,我想起了mysql對於同一組參數進行的mysql_connect()連接,是不會重新建立連接的。php手冊中對這個函
數的解釋如下:
如果用同樣的參數第二次調用 mysql_connect(),將不會建立新連接,而將返回已經打開的連接標識。
其實呢,只是mysql_connect這個函數做了可復用了。不討論數據庫連接方面。實例化其他的類,也需要創建大量的實例。占有資源。是指同一次執行的代
碼過程中才能起到節省資源的效果
比如a.php的代碼過程如下:
$class = test::get_instance();//得到這個test這個類的實例
$class->get_name();
get_count_number();//假設這個函數里面又需要用到那個類,則又需要進行實例化,如果統一調用get_instance()來獲取實例,則前面得到的實例是可以
復用的。
三、單態模式(monostate)
1、單件模式還有一種變體:就是類的單件模式,也就是monostate模式。MonoState的意思就是"單一的狀態"。也就是常說的單態。實現的目標為:所有實
例都是共享類中同一個值。
monostate的設計目標為:實現多個實例可以共享變量(類里面的屬性),成為單態,盡管存在多個實例,但實例中的變量的最終只會有一個狀態(可以理解為
一個值),不會出現多個值(也就是每個實例里面的變量都是不同的值)。
2、它與單件的區別為:
單件是將構造函數聲明為private,來保證只有一個實例。而單態則不需要。它關注的側重點是最終只有一個數值,而用戶實例化多少類,不是它所關心的
。
MonoState並不限制創建對象的個數,但是它的狀態卻只有一個狀態。
3、monostate模式實踐
實踐要點:把類里面的變量(屬性)標識為static即可
<?php
class test
{
static $_state = array();
function set($key,$value)
{
self::$_state[$key]= $value;
}
function get($key)
{
return self::$_state[$key];
}
}
$obj = new test();
$obj->set('name','wangtao');
$obj->set('sex','male');
echo $obj->get('name');//得到結果是wangtao
//再次實例化一次,看訪問對象的成員,是否得到一樣的數據。
$obj2 = new test();
echo $obj2->get('name');//輸出wangtao
//再次新創建一個實例$obj2,訪問name這個變量,數據是共享的,所以輸出還是wangtao。當然使用set()把值改變了,其他實例也會訪問到改變后的值。
總結:實現monostate模式,具體實現有多種辦法,只要達到共享數據的目的就ok。比如使用$_GLOABS[]全局變量,把數據保存在全局變量中,然后放到類
成員中也可以,《php設計模式》這本書就是使用這種形式實現。使用靜態變量(static關鍵字)也可以。上面使用的就是靜態變量的方式。我覺得使用
static方式更加直觀易懂
四、思考:sington與monostate能混合一起實現嗎?
既然sington模式可以避免創建多個實例。而monostate是關注多個實例之間共享數據。
那么有沒有種辦法讓兩者混合呢。
也就是說:我構造一個類,既能夠達到單件的效果,也能實現monostate的效果。開玩笑玩玩,呵呵,加深深入理解。
我覺得,單件關注的是實例化一個類。monostate關注的狀態的一致性。其實兩者是不相容的。
如果實現了單件模式。那么就不存在多個實例對象存在。既然都是調用同一個實例,這樣子里面的成員變量肯定是共享的,因為使用的是同一個實例的成員
。為此我特意做試驗,如下:
class test
{
static $_state;//實現單態,就是將里面變量定義為static即可,現在這個類實現了monostate模式
static $_instance = false;
private function __construct()
{
}
/*
實現單例模式
*/
function get_instance()
{
if(self::$_instance==false && !is_object(self::$_instance)){
self::$_instance = new test();
}
return self::$_instance;
}
}
$obj1 = test::get_instance();
$obj1->_state = 20;
$obj2 = test::get_instance();//因為這里引用的還是同一個實例,所以下面輸出屬性的值,還是前面更改的20
echo $obj2->_state;
以上是給自己總結用。不正確之處歡迎指正。