PHP中”單例模式“實例講解


假設我們需要寫一個類用來操作數據庫,並同時滿足以下要求:

①SqlHelper類只能有一個實例(不能多)
②SqlHelper類必須能夠自行創建這個實例
③必須自行向整個系統提供這個實例,換句話說:多個對象共享一塊內存區域,比如,對象A設置了某些屬性值,則對象B,C也可以訪問這些屬性值(結尾的例子很好的說明了這個問題)

 1 <?php
 2     class SqlHelper{
 3         private static $_instance;
 4         public $_dbname;
 5         private function __construct(){
 6             
 7         }
 8         public function getDbName(){
 9             echo $this->_dbname;
10         }
11         public function setDbName($dbname){
12             $this->_dbname=$dbname;
13         }
14         public function clear(){
15             unset($this->_dbname);
16         }
17         
18     }
19     $sqlHelper=new SqlHelper();//打印:Fatal error: Call to private SqlHelper::__construct() from invalid context 
20 ?>

以上的SqlHelper類是無法從自身的類外部創建實例的,因為我們將構造函數設為了private,所以通過new SqlHelper()是無法從類外部使用私有的構造函數的,如果強制使用,將會報如下錯誤:
Fatal error: Call to private SqlHelper::__construct() from invalid context
嚴重錯誤:從上下文中調用了一個私有的構造函數SqlHelper::__construct()

按照已往的思維邏輯,實例化一個類都是直接在類外部使用new操作符的,但是既然這里講構造函數設為private了,我們知道,私有的成員屬性或函數只能在類的內部被訪問,所以我們可以通過在類SqlHelper內部再創建一個函數(比如:getInstance()),而且必須是public的,getInstance()函數中主要進行的是實例化SqlHelper類
比如:

 1 <?php
 2     class SqlHelper{
 3         private $_instance;
 4         //......省略
 5         public function getInstance(){
 6             $this->_instance=new SqlHelper();
 7         }
 8         //......省略
 9     }
10 ?>

但是問題出現了,
①我們在調用getInstance()之前沒有實例化SqlHelper對象,所以也就無法通過對象的方式來調用getInstance()函數了,
②既然在調用getInstance的時候還未實例化出對象,所以在getInstance函數中使用$this肯定也會報錯(Fatal error: Using $this when not in object context)
那如何解決呢?

解決途徑:我們可以講getInstance()方法設為靜態的,根據靜態的定義,她只能被類而不是對象調用,將$_instance也設為靜態的即可。所以這個方法正好符合我們的口味。
所以我們進一步將代碼修改如下:

 1 <?php
 2     class SqlHelper{
 3         private static $_instance;
 4         private function __construct(){
 5             echo "構造函數被調用";
 6         }        
 7         //......省略
 8         public static function getInstance(){
 9             if (self::$_instance===null) {
10 //                self::$_instance=new SqlHelper();//方式一
11                 self::$_instance=new self();//方式二                
12             }
13             return self::$_instance;
14         }
15         //......省略
16     }
17     $sqlHelper=SqlHelper::getInstance();//打印:構造函數被調用
18 ?>

通過在getInstance函數中對當前內存中有誤存在當類類的一個實例進行判斷,如果沒有則實例化,並返回對象句柄,如果有則直接返回該對象句柄
至此,完整代碼如下所示:

 1 <?php
 2     class SqlHelper{
 3         private static $_instance;
 4         public $_dbname;
 5         private function __construct(){
 6             
 7         }
 8         //getInstance()方法必須設置為公有的,必須調用此方法
 9         public static function getInstance(){
10             //對象方法不能訪問普通的對象屬性,所以$_instance需要設為靜態的
11             if (self::$_instance===null) {
12 //                self::$_instance=new SqlHelper();//方式一    
13                 self::$_instance=new self();//方式二        
14             }
15             return self::$_instance;
16         }
17         public function getDbName(){
18             echo $this->_dbname;
19         }
20         public function setDbName($dbname){
21             $this->_dbname=$dbname;
22         }
23     }
24 //    $sqlHelper=new SqlHelper();//打印:Fatal error: Call to private SqlHelper::__construct() from invalid context 
25     $A=SqlHelper::getInstance();
26     $A->setDbName('數據庫名');
27     $A->getDbName();
28 //    unset($A);//移除引用
29     $B=SqlHelper::getInstance();
30     $B->getDbName();
31     $C=SqlHelper::getInstance();
32     $C->getDbName();
33     
34 ?>

以上代碼的執行結果:
數據庫名//$A->getDbName();

數據庫名//$B->getDbName();
數據庫名//$C->getDbName();
也就是說,對象A,B,C實際上都是使用同一個對象實例,訪問的都是同一塊內存區域
所以,即使unset($A),對象B和C還是照樣能夠通過getDbName()方法輸出“數據庫名”的
unset($A)實際上只是將對象A與某塊內存地址(該對象的實例所在的地址)之間的聯系方式斷開而已,跟對象B和對象C無關,可以用用一張圖表示如下

 

原創文章:MarcoFly

轉載請注明出處:http://www.cnblogs.com/hongfei/archive/2012/07/07/2580994.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM