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

PHP 設計模式深度解析:單例模式 實作與演進

     編輯:PHP 相關知識

單例模式確保某個類別在整個應用程式生命週期中「只有一個實例」,並提供全域存取點。常見用途:資料庫連線、日誌記錄器、配置管理器。本文從問題導向出發,對比 PHP 4 與 PHP 5/7/8 的實作差異,並探討現代最佳實踐。

一、核心問題與解決方案

問題:如何保證類別只被實例化一次,且易於存取?

方案:

  • 將建構子設為 private,防止外部 new
  • 提供靜態方法 getInstance() 回傳唯一實例
  • 靜態屬性保存實例參考

二、PHP 4 時代的實作 (歷史參考)

PHP 4 無 private、靜態屬性不支援物件參考,需用全域變數或靜態陣列變通:

class DbConn {
    function DbConn($fromGetInstance = false) {
        if (M_E != $fromGetInstance) { // 簡易保護
            trigger_error('DbConn 是單例,請勿直接實例化');
        }
    }

    function &getInstance() {
        static $instance = [];
        if (!$instance) {
            $instance[0] =& new DbConn(M_E);
        }
        return $instance[0];
    }
}

呼叫方式:$conn =& DbConn::getInstance(); (需傳回參考)

三、PHP 5+ 現代標準實作

PHP 5 引入可見度關鍵字、統一建構子 __construct、靜態屬性支援物件,實作大幅簡化:

class DbConn {
    private static $instance = null;

    private function __construct() {
        // 初始化資料庫連線...
    }

    // 禁止克隆
    private function __clone() {}

    // 禁止反序列化
    public function __wakeup() {
        throw new Exception("不能反序列化單例");
    }

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 業務方法
    public function query(string $sql) { /* ... */ }
}

關鍵點解析

  • private __construct:阻擋 new DbConn()
  • private __clone:阻擋 clone $db
  • __wakeup:阻擋 unserialize() 產生新實例
  • 型別宣告 : self (PHP 7.4+) 提升程式碼可讀性與靜態分析能力

四、執行緒安全與多程序考量

PHP 通常運行於單執行緒環境 (mod_php, php-fpm),單例在單一請求內自然安全。但若使用 Swoole、RoadRunner 等長驻程式模型,需注意:

  • 單例實例會跨請求共享,可能導致資料洩漏
  • 資料庫連線斷線需自動重連邏輯
  • 考慮使用「請求範圍單例」或依賴注入容器管理生命週期

五、反模式警示與替代方案

單例常被濫用,缺點包括:

  • 隱性相依,難以單元測試
  • 全域狀態導致副作用難追蹤
  • 違反單一職責原則 (同時負責建立與業務)

現代替代方案:

  1. 依賴注入 (DI) 容器:如 PHP-DI, Symfony DI, Laravel Container,註冊為 singleton 共享實例
  2. 服務定位器:集中管理服務實例
  3. 工廠模式:集中控制建立邏輯

六、實戰範例:PDO 資料庫連線單例

class Database {
    private static $instance = null;
    private $pdo;

    private function __construct() {
        $dsn = "mysql:host=localhost;dbname=test;charset=utf8mb4";
        $this->pdo = new PDO($dsn, 'user', 'pass', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false,
        ]);
    }

    public static function getInstance(): self {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 代理 PDO 方法
    public function __call(string $method, array $args) {
        return $this->pdo->$method(...$args);
    }
}

// 使用方式
$db = Database::getInstance();
$stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([1]);
$user = $stmt->fetch();

七、總結

單例模式在 PHP 5+ 實作簡潔優雅,但現代架構傾向透過 DI 容器管理共享服務,而非硬編碼單例。理解單例本質與限制,能幫助開發者在適當場合做出正確架構決策。對於資料庫連線、快取客戶端等真正需要「全程序唯一實例」的場景,單例仍是可行選項,只需注意測試與長驻環境下的狀態管理。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved