設計模式04--單例模式(Singleton)

一、簡介

單例模式,正如其名,允許我們創建一個而且只能創建一個對象的類。這在整個系統的協同工作中非常有用,特別明確了只需一個類對象的時候。

那麼,爲什麼要實現這麼奇怪的類,只實例化一次?

在很多場景下會用到,如:配置類、Session類、Database類、Cache類、File類等等。這些只需要實例化一次,就可以在應用全局中使用。

二、場景

1、提出問題

如果沒有使用單例模式,會有什麼樣的問題?
如下是一個簡單的數據庫連接類,它沒有使用單例模式:

class Database
{
    public $db = null;
    public function __construct($config = array())
    {
        $dsn = sprintf('mysql:host=%s;dbname=%s', $config['db_host'], $config['db_name']);
        $this->db = new PDO($dsn, $config['db_user'], $config['db_pass']);
    }
}

然後創建3個對象:

$config = array(
    'db_name' => 'test',
    'db_host' => 'localhost',
    'db_user' => 'root',
    'db_pass' => 'root'
);

$db1 = new Database($config);
var_dump($db1);
$db2 = new Database($config);
var_dump($db2);
$db3 = new Database($config);
var_dump($db3);

這種情況下,每當我們創建一個這個類的實例,就會新增一個到數據庫的連接。開發者每在一個地方實例化一次這個類,就會在那裏多一個數據庫連接。不知不覺中,開發者就犯了個錯誤,給數據庫和服務器性能帶來巨大的影響。

上面的代碼輸入如下:

object(Database)[1]
  public 'db' => object(PDO)[2]
object(Database)[3]
  public 'db' => object(PDO)[4]
object(Database)[5]
  public 'db' => object(PDO)[6]

每個對象都分配一個新的資源ID,都是新的引用,它們佔用3個的內存空間。如果有100個對象創建,就會佔用內存中100塊不同的空間,而其餘99塊並非是必須的。

2、解決問題

要解決這樣的問題,我們可以控制住基類,在源頭上限制這個類,使其無法生成多個對象,如果已經生成過,直接返回。

於是,我們的目標就是,控制數據庫類,使其生成一次而且只能生成一次對象。

三、類結構

單例模式結構如下:

角色 簡述
Singleton 一般是一個單例類或是接口

四、UML圖

比如,我們需要管理一個數據庫資源的類:

五、類實現

如下就是單例模式連接數據庫代碼:

class Database
{
    // 聲明$instance爲私有靜態類型,用於保存當前類實例化後的對象
    private static $instance = null;
    // 數據庫連接句柄
    private $db = null;

    // 構造方法聲明爲私有方法,禁止外部程序使用new實例化,只能在內部new
    private function __construct($config = array())
    {
        $dsn = sprintf('mysql:host=%s;dbname=%s', $config['db_host'], $config['db_name']);
        $this->db = new PDO($dsn, $config['db_user'], $config['db_pass']);
    }

    // 這是獲取當前類對象的唯一方式
    public static function getInstance($config = array())
    {
        // 檢查對象是否已經存在,不存在則實例化後保存到$instance屬性
        if(self::$instance == null) {
            self::$instance = new self($config);
        }
        return self::$instance;
    }

    // 獲取數據庫句柄方法
    public function db()
    {
        return $this->db;
    }

    // 聲明成私有方法,禁止克隆對象
    private function __clone(){}
    // 聲明成私有方法,禁止重建對象
    private function __wakeup(){}
}

再通過getInstance()方法使用類對象:

$config = array(
    'db_name' => 'test',
    'db_host' => 'localhost',
    'db_user' => 'root',
    'db_pass' => 'root'
);

$db1 = Database::getInstance($config);
var_dump($db1);
$db2 = Database::getInstance($config);
var_dump($db2);
$db3 = Database::getInstance($config);
var_dump($db3);

輸出信息如下:

object(Database)[1]
  private 'db' => object(PDO)[2]
object(Database)[1]
  private 'db' => object(PDO)[2]
object(Database)[1]
  private 'db' => object(PDO)[2]

對比兩個輸出可以看出,單例模式中,不同對象獲得的資源ID是一樣的。也就是說,雖然我們用getInstance()獲取Database類對象3次,其實引用的是一個內存空間,PDO也只連接了數據庫一次。

以上的例子是數據庫連接類,要使用數據庫,在應用這樣獲得連接句柄:

$db = database::getInstance($config)->db();

如果是其他類,則按需要修改數據庫相關的代碼,單例實現部分保留。

六、特點

單例模式的特點是4私1公

  • 一個私有靜態屬性,構造方法私有,克隆方法私有,重建方法私有
  • 一個公共靜態方法。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章