關於php的單例模式

單例模式(Singleton Pattern):顧名思義,就是隻有一個實例。做爲對象的建立模式,單例模式確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。php

爲何要使用單例模式

一、PHP語言自己的侷限性數據庫

PHP語言是一種解釋型的腳本語言,這種運行機制使得每一個PHP頁面被解釋執行後,全部的相關資源都會被回收。也就是說,PHP在語言級別上沒有辦法讓某個對象常駐內存,這和asp.NET、Java等編譯型是不一樣的,好比在Java中單例會一直存在於整個應用程序的生命週期裏,變量是跨頁面級的,真正能夠作到這個實例在應用程序生命週期中的惟一性。然而在PHP中,全部的變量不管是全局變量仍是類的靜態成員,都是頁面級的,每次頁面被執行時,都會從新創建新的對象,都會在頁面執行完畢後被清空,這樣彷佛PHP單例模式就沒有什麼意義了,因此PHP單例模式我以爲只是針對單次頁面級請求時出現多個應用場景並須要共享同一對象資源時是很是有意義的。編程

二、應用場景編程語言

一個應用中會存在大量的數據庫操做,好比過數據庫句柄來鏈接數據庫這一行爲,使用單例模式能夠避免大量的new操做,由於每一次new操做都會消耗內存資源和系統資源。 若是系統中須要有一個類來全局控制某些配置信息,那麼使用單例模式能夠很方便的實現.函數

要點

  1. 一個類只能有一個對象
  2. 必須是自行建立這個類的對象
  3. 要想整個系統提供這一個對象

具體實現的重點

  1. 單例模式的類只提供私有的構造函數,
  2. 類定義中含有一個該類的靜態私有對象,
  3. 該類提供了一個靜態的公有的函數用於建立或獲取它自己的靜態私有對象。

代碼實現

class Singleton{
        //存放實例 私有靜態變量
        private static $_instance = null;

        //私有化構造方法、
        private function __construct(){
            echo "單例模式的實例被構造了";
        }
        //私有化克隆方法
        private function __clone(){

        }

        //公有化獲取實例方法
        public static function getInstance(){
            if (!(self::$_instance instanceof Singleton)){
                self::$_instance = new Singleton();
            }
            return self::$_instance;
        }
    }

    $singleton=Singleton::getInstance();
複製代碼

OOP知識補習

類型運算符instanceof

<?php
class MyClass
{
}

class NotMyClass
{
}
$a = new MyClass;

var_dump($a instanceof MyClass);
var_dump($a instanceof NotMyClass);
?>
複製代碼

以上例程會輸出:性能

bool(true)
bool(false)
複製代碼

instanceof用於肯定一個變量是否是實現了某個類,繼承類,接口的對象的實例。 若是被檢測的變量不是對象,instanceof 並不發出任何錯誤信息而是返回 FALSE。不容許用來檢測常量。this

魔術方法__construct()

構造方法聲明爲private,防止直接建立對象 ,這樣new Singleton() 會報錯。spa

private function __construct() { echo 'Iam constructed'; }code

魔術方法__clone()

當類的複製完成時,若是定義了__clone()方法,則新建立的對象(複製生成的對象)中的__clone() 方法會被調用,可用於修改屬性的值(若是有必要的話)。私有化__clone能夠防止克隆該類的對象。 注意一點:clone的對象不執行__construct裏的方法對象

因此咱們在防止單例模式的 $singleton對象被clone,有兩種方法能夠作到。

第一種方法:設置魔術方法__clone();訪問權限爲private 第二種方法:若__clone()爲公用方法,則在函數中加上自定義錯誤。

// 阻止用戶複製對象實例
public function __clone(){
    trigger_error('Clone is not allowed.',E_USER_ERROR);
}
複製代碼

關於 __clone() , PHP官方的文檔: Once the cloing is complete, if a __clone() method is defined, then the newly created object’s __clone() method will be called, to allow any necessary properties that need to be changed.

關鍵字clone和賦值

class foo {
	public $bar = 'php';
}
$foo = new foo();

$a = $foo; // 標識符賦值(把$a賦值爲null,原來的$foo並不會變成null,但經過$a可以修改$foo的成員$bar)
$a = &$foo; // 引用賦值(把$a賦值爲null,原來的$foo也會跟着變成null)
$a = clone $foo; // 值賦值(賦值後互不影響,在計算機內存上的體現屬於淺複製)
複製代碼

對象複製

在PHP中, 對象間的賦值操做其實是引用操做 (事實上,絕大部分的編程語言都是如此! 主要緣由是內存及性能的問題) , 好比 :

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data = "aaa";
$obj2 = $obj1;
$obj2->data ="bbb";     //$obj1->data的值也會變成"bbb"
複製代碼

由於 obj1 ,obj2 都是指向同一個內存區的引用,因此修改任何一個對象都會同時修改另一個對象。

在有些時候,咱們其實不但願這種reference式的賦值方式, 咱們但願能徹底複製一個對象,這是侯就須要用到 Php中的clone (對象複製)。

class myclass {
public $data;
}
$obj1 = new myclass();
$obj1->data ="aaa";
$obj2 = clone $obj1;
$obj2->data ="bbb";     // $obj1->data的值仍然爲"aaa"
複製代碼

由於clone的方式其實是對整個對象的內存區域進行了一次複製並用新的對象變量指向新的內存, 所以賦值後的對象和源對象相互之間是基原本說獨立的。

淺複製

什麼? 基本獨立?!這是什麼意思? 由於PHP的object clone採用的是淺複製(shallow copy)的方法, 若是對象裏的屬性成員自己就是reference類型的,clone之後這些成員並無被真正複製,仍然是引用的。 (事實上,其餘大部分語言也是這樣實現的, 若是你對C++的內存,拷貝,copy constructor等概念比較熟悉,就很容易理解這個概念), 下面是一個例子來講明:

class myClass{
public $data;
}

$sss ="aaa";
$obj1 = new myClass();
$obj1->data =&$sss;   //注意,這裏是個reference!
$obj2 = clone $obj1;
$obj2->data="bbb";  //這時,$obj1->data的值變成了"bbb" 而不是"aaa"!

var_dump($obj1);
var_dump($obj2);
複製代碼

咱們再舉一個更實用的例子來講明一下PHP clone這種淺複製帶來的後果:

class testClass
{
   public $str_data;
   public $obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;

var_dump($obj1);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);    // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"

$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));      //給$obj2->obj_date 的時間增長了10天

var_dump($obj1);     // str_data:"aaa"   obj_data:"2014-07-15 00:00:00"  !!!!
var_dump($obj2);     // str_data:"bbb"   obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj)  // 2014-07-15 00:00:00"
複製代碼

這一下能夠更加清楚的看到問題了吧。 通常來說,你用clone來複制對象,但願是把兩個對象完全分開,不但願他們之間有任何關聯, 但因爲clone的shallow copy的特性, 有時候會出現非你指望的結果.

深複製

$obj1->obj_data =$dateTimeObj 
複製代碼

這句話其實是個引用類型的賦值. 還記得前面提到的PHP中對象直接的賦值是引用操做麼?除非你用obj1->obj_dat = clonedataTimeObj!

$obj2 = clone $obj1 
複製代碼

這句話生成了一個obj1對象的淺複製對象,並賦給obj2. 因爲是淺複製,obj2中的obj_data也是對$dateTimeObj的引用!

3)

$dateTimeObj,
$obj1->obj_data, 
$obj2->obj_data
複製代碼

其實是同一個內存區對象數據的引用,所以修改其中任何一個都會影響其餘兩個!

如何解決這個問題呢? 採用PHP中的 __clone方法 把淺複製轉換爲深複製(這個方法給C++中的copy constructor概念上有些類似,但執行流程並不同)

class testClass
{
 public $str_data;
 public $obj_data;

 public function __clone() {
   $this->obj_data = clone $this->obj_data;
}

$dateTimeObj = new DateTime("2014-07-05", new DateTimeZone("UTC"));

$obj1 = new testClass();
$obj1->str_data ="aaa";
$obj1->obj_data = $dateTimeObj;

$obj2 = clone $obj1;
var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
$obj2->str_data ="bbb";
$obj2->obj_data->add(new DateInterval('P10D'));

var_dump($obj1);  // str_data:"aaa"  obj_data:"2014-07-05 00:00:00"
var_dump($obj2);  // str_data:"aaa"  obj_data:"2014-07-15 00:00:00"
var_dump($dateTimeObj);  //"2014-07-05 00:00:00"
複製代碼
相關文章
相關標籤/搜索