單例模式 確保某個類在全局範圍內僅存在一個實例,並提供一個全局訪問點獲取該實例。它不創建副本,而是返回對內部儲存實例的引用。
| 要素 | 修飾符 | 作用 |
|---|---|---|
| 儲存實例的靜態成員變量 | private static | 防止外部直接修改實例引用 |
| 構造函數 | private | 禁止 new ClassName() 創建新實例 |
克隆方法 __clone() | private | 禁止 clone $instance 複製實例 |
| 獲取實例的公共靜態方法 | public static | 統一入口,通常命名 getInstance() |
<?php
class Database {
// 1. 私有靜態實例容器
private static ?self $instance = null;
// 2. 連接資源(真實業務中為 PDO/mysqli 實例)
private $connection;
// 3. 私有構造函數:完成昂貴的初始化
private function __construct() {
$dsn = sprintf(
'mysql:host=%s;dbname=%s;charset=utf8mb4',
self::DB_HOST, self::DB_NAME
);
$this->connection = new PDO($dsn, self::DB_USER, self::DB_PWD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
// 4. 禁止克隆
private function __clone() {}
// 5. 禁止反序列化(防止繞過構造函數)
public function __wakeup() {
throw new \Exception("Cannot unserialize singleton");
}
// 6. 統一訪問入口(雙重檢查鎖模式,PHP 單執行緒下簡化版)
public static function getInstance(): self {
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
// 7. 業務方法代理到底層連接
public function query(string $sql, array $params = []): \PDOStatement {
$stmt = $this->connection->prepare($sql);
$stmt->execute($params);
return $stmt;
}
// 常量配置(實際項目建議從環境變量/配置中心讀取)
private const DB_HOST = '127.0.0.1';
private const DB_NAME = 'app_db';
private const DB_USER = 'app_user';
private const DB_PWD = 'secret';
}
// --- 使用示例 ---
$db = Database::getInstance();
$users = $db->query('SELECT * FROM users WHERE status = ?', [1])->fetchAll();
若需子類各自擁有單例,使用 `static::` 而非 `self::`:
abstract class Singleton {
private static $instances = [];
final protected function __construct() {}
final protected function __clone() {}
final public static function getInstance() {
$cls = static::class; // 關鍵:獲取實際調用的子類名
if (!isset(self::$instances[$cls])) {
self::$instances[$cls] = new $cls();
}
return self::$instances[$cls];
}
}
class MySQLDB extends Singleton { }
class RedisDB extends Singleton { }
// MySQLDB::getInstance() 與 RedisDB::getInstance() 互不干擾
單例的全局狀態會導致測試相互污染。解決方案:
Laravel、Symfony、Hyperf 等框架不再手寫單例類,而是將類註冊為 singleton 綁定到容器:
// Laravel 服務提供者中
$this->app->singleton(Database::class, function () {
return new Database(config('database'));
});
// 控制器構造函數自動注入(容器保證同一請求內單例)
public function __construct(protected Database $db) {}
優勢:解耦創建邏輯、支援介面綁定、便於測試替換、生命週期可視化管理。
單例模式在 PHP 中最核心的價值是 「昂貴資源的單一化管理」。掌握「三私一公」結構、LSB 繼承技巧、測試隔離方案,並理解其在現代框架容器中的演進形態,才能在架構決策中遊刃有餘——用對地方是藝術,到處濫用是災難。