首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 單例模式實現詳解:私有建構子、靜態實例與防止克隆

     編輯:PHP 相關知識

單例模式核心三要素

  1. 靜態成員變數:儲存唯一實例
  2. 私有化建構子與 __clone:防止 new 與 clone 建立新實例
  3. 公共靜態存取方法:提供全域唯一存取點

標準實現程式碼


class Singleton {
    // 1. 靜態私有成員保存唯一實例
    private static $_instance = null;

    // 2. 私有建構子,防止外部 new
    private function __construct() {
        echo 'Instance created';
    }

    // 3. 私有克隆方法,防止 clone
    private function __clone() {
        trigger_error('Cloning singleton is forbidden', E_USER_ERROR);
    }

    // 4. 公共靜態方法取得實例
    public static function getInstance() {
        if (self::$_instance === null) {
            // 晚期靜態綁定支援繼承
            $class = get_called_class();
            self::$_instance = new $class();
        }
        return self::$_instance;
    }

    public function doSomething() {
        echo 'Working...';
    }
}

// 使用方式
$instance = Singleton::getInstance();
$instance->doSomething();

// 以下皆會失敗/報錯
// $fail1 = new Singleton();        // Fatal: private constructor
// $fail2 = clone $instance;        // Fatal: private __clone
// $fail3 = Singleton::getInstance(); // 回傳同一實例

關鍵細節深度解析

晚期靜態綁定(LSB)支援繼承


class ParentSingleton {
    private static $_instance = null;
    protected function __construct() {}
    private function __clone() {}
    public static function getInstance() {
        if (static::$_instance === null) {
            static::$_instance = new static(); // 關鍵:static 而非 self
        }
        return static::$_instance;
    }
}

class ChildSingleton extends ParentSingleton {}
$child = ChildSingleton::getInstance(); // 建立 ChildSingleton 實例

使用 static:: 而非 self::,確保子類別擁有自己的單例實例,而非共用父類別實例。

執行緒安全考量(PHP-FPM/CLI 通常無慮)

PHP 採用「共享無」架構,每個請求獨立行程/執行緒,單例僅在單一請求生命週期內有效,無需雙重檢查鎖定。若使用 Swoole、RoadRunner 等長駐記憶體架構,需自行加鎖。

序列化破壞單例的防範


public function __sleep() {
    throw new Exception('Singleton cannot be serialized');
}
public function __wakeup() {
    throw new Exception('Singleton cannot be unserialized');
}

常見變體與進階用法

登記式單例(Registry of Singletons)


class ServiceRegistry {
    private static $services = [];
    public static function get($key, callable $factory) {
        if (!isset(self::$services[$key])) {
            self::$services[$key] = $factory();
        }
        return self::$services[$key];
    }
}
// 使用
$db = ServiceRegistry::get('db', fn() => new PDO($dsn));
$cache = ServiceRegistry::get('cache', fn() => new Redis());

依賴注入容器中的單例

現代框架(Laravel、Symfony)透過容器管理單例生命週期,不建議在業務代碼中硬編碼單例,改由容器注入:


// 容器設定
$container->singleton(Database::class, fn() => new PDO($dsn));
// 控制器自動注入
class UserController {
    public function __construct(private Database $db) {}
}

反模式與避坑指南

反模式問題替代方案
全域變數 $GLOBALS['db']難測試、隱性依賴、命名衝突容器注入、Service Locator
靜態類別滿滿 static 方法無法多型、難 Mock、違反 OCP實例化服務 + 介面契約
單例濫用作為「全域狀態袋」耦合度極高、並發風險領域事件、共享唯讀設定物件

適用場景判斷清單

✅ 適合單例:

  • 資料庫連線池、快取客戶端(Redis/Memcached)
  • 設定管理器(唯讀配置)
  • 日誌記錄器
  • 硬體資源封裝(如 GPIO、序列埠)

❌ 不適合單例:

  • 實體模型(User、Order 等有身分識別的物件)
  • 請求相關服務(Session、Request、Auth)
  • 需支援多租戶/多資料庫的服務

小結

單例模式在 PHP 中實現簡單,但濫用會導致強耦合、難以測試。現代開發建議:

  1. 優先使用依賴注入容器管理共享服務生命週期
  2. 若確需手寫單例,務必防禦性程式設計(防克隆、防序列化、LSB 相容)
  3. 明確區分「應用層單例」(請求範圍)與「進程單例」(長駐進程),後者需處理併發安全
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved