淺入理解單例模式

問題

惱人的全局變量

在 PHP 中,甚至不僅 PHP 中,咱們都會用到全局變量,以保存全局狀態。但是,每每全局變量是全局共享的,任何地方任何代碼都有可能將其覆蓋。例如,咱們定義一個全局變量叫作 PHONE。咱們在某一行代碼中,將其定義成了 iPhone,可是咱們不當心在另外一行代碼中將其覆寫成了 Nokia。這就很是的尷尬了,由於原本咱們並不想它被覆寫。php

繁瑣的參數傳遞

在一個系統中,咱們會定義許多的方法,生成不少的對象。有時候,咱們會使用不少的方法,對同一個對象作操做。在不使用全局變量的狀況下,咱們須要將對象做爲參數傳入方法中。可是這樣傳遞同一個對象,可能會形成混亂,還可能形成沒必要要的依賴。設計模式

其實咱們只須要一個全局可訪問的對象就能夠解決這個,可是全局變量又會出現咱們上面的說的問題。函數

解決

目標

咱們要解決這些問題,咱們對這樣的對象有下面的幾個目標。學習

  • 這個對象,不管在哪裏都能訪問,就想全局變量同樣。
  • 這個對象,和全局變量不一樣,不能被覆寫。
  • 這個對象,整個系統中只存在一個,對它的修改在整個系統中都能被感知到。

以上的幾個目標,就是咱們所須要的,也就是單例模式的特徵。this

UML

實現

class Preference
{
    private static $instance;
    private $props = [];
    
    private __construct() {}
    
    public static function getInstance()
    {
        if (empty(self::$instance)) {
            self::$instance = new Preference();
        }
        
        return self::$instance;
    }
    
    public function setProperty($key, $value)
    {
        $this->props[$key] = $value;
    }
    
    public function getProperty($key)
    {
        return $this->props[$key];
    }

    private function __clone() {}
    
    private function __sleep() {}
    
    private function __wakeup() {}
}

咱們在這裏引入了一個私有的構造函數,這樣,外部就沒法實例化這個對象了。同時,咱們使用 getInstance 方法來獲取具體的實例,而沒法去覆寫它,這就達成了第二個目標。spa

因爲 $instancegetInstance 都是靜態的,因此咱們能夠經過 Preference::getInstance() 訪問,具體的實例。這樣就使得全局均可以訪問到它了,它就像全局變量同樣了,這就達成了第一個目標了。設計

對於這個類,咱們沒法生成第二個對象,由於它的構造函數是私有的,而且 __clone 方法是私有的,並且,getInstance 在判斷已經有了一個實例的狀況下默認返回該實例。這就達成了第三個目標了。code

同時,咱們也儘可能避免序列化這個實例,因此咱們給 __wakeup__sleep 這兩個魔術方法私有。對象

這就是單例模式。rem

後記

對於單例模式,其實沒有那麼高大上。只不過是更改的對象的訪問範圍,以及對象始終存在,僅此而已。

最後,本文章是做者在學習設計模式時的感想。部分參考自《深刻 PHP 面向對象、模式與實踐(第 3 版)》。若有錯誤,感謝大神不吝賜教。

相關文章
相關標籤/搜索