一、簡介
單例模式,正如其名,允許我們創建一個而且只能創建一個對象的類。這在整個系統的協同工作中非常有用,特別明確了只需一個類對象的時候。
那麼,爲什麼要實現這麼奇怪的類,只實例化一次?
在很多場景下會用到,如:配置類、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公:
- 一個私有靜態屬性,構造方法私有,克隆方法私有,重建方法私有
- 一個公共靜態方法。