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

PHP 設計模式:單例模式完整實作與現代替代方案

     編輯:PHP 綜合知識

單例模式:確保類別只有一個實例,提供全域存取點

單例模式 是最知名也最具爭議的設計模式。本文詳細實作 PHP 4/5/7/8 版本差異、執行緒安全、序列化防護、測試困境與現代替代方案 (DI 容器、靜態類別、服務定位器)。

PHP 5+ 標準實作

<?php
class DatabaseConnection {
    private static ?self $instance = null;
    private PDO $pdo;
    
    // 私有建構子:防止外部 new
    private function __construct() {
        $this->pdo = new PDO('mysql:host=localhost;dbname=app', 'user', 'pass', [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
        ]);
    }
    
    // 防止複製
    private function __clone() {}
    
    // 防止反序列化建立新實例
    public function __wakeup() {
        throw new Exception("Cannot unserialize singleton");
    }
    
    // 靜態存取點
    public static function getInstance(): self {
        return self::$instance ??= new self();
    }
    
    // 代理 PDO 方法
    public function query(string $sql): PDOStatement {
        return $this->pdo->query($sql);
    }
    
    public function prepare(string $sql): PDOStatement {
        return $this->pdo->prepare($sql);
    }
    
    // ... 其他需要的方法
}

// 使用
$db = DatabaseConnection::getInstance();
$stmt = $db->query("SELECT * FROM users");
?>

PHP 4 相容寫法 (歷史參考)

<?php
class DatabaseConnection {
    function DatabaseConnection($fromGetInstance = false) {
        if (M_E != $fromGetInstance) { // 簡單保護
            trigger_error("Singleton: use getInstance()");
        }
    }
    
    function &getInstance() {
        static $instance = null;
        if (!$instance) {
            $instance = new DatabaseConnection(M_E); // 傳遞常數驗證
        }
        return $instance;
    }
}
?>

單例模式核心測試

<?php
class SingletonTest extends PHPUnit\Framework\TestCase {
    public function testSameInstanceReturned() {
        $instance1 = DatabaseConnection::getInstance();
        $instance2 = DatabaseConnection::getInstance();
        $this->assertSame($instance1, $instance2); // 同一物件引用
    }
    
    public function testConstructorIsPrivate() {
        $this->expectException(Error::class);
        new DatabaseConnection(); // 應拋出錯誤
    }
    
    public function testCannotClone() {
        $instance = DatabaseConnection::getInstance();
        $this->expectException(Error::class);
        clone $instance;
    }
    
    public function testCannotUnserialize() {
        $instance = DatabaseConnection::getInstance();
        $serialized = serialize($instance);
        $this->expectException(Exception::class);
        unserialize($serialized);
    }
}
?>

單例模式的爭議與缺點

問題 說明 影響
全域狀態 任何地方可存取修改 難以追蹤 bug、測試污染
隱性依賴 函數簽名不顯示需單例 API 不誠實、耦合度高
測試困難 單例狀態跨測試殘留 需重置、Mock 困難
違反單一職責 類別同時負責業務邏輯與生命週期管理 擴展性差
多執行緒不安全 PHP-FPM 多進程各自有實例,但 Swoole/RoadRunner 共享記憶體需鎖 併發環境需額外同步

現代替代方案比較

方案 優點 缺點 適用場景
依賴注入容器 生命週期管理、自動裝配、測試友善、解耦 學習曲線、啟動開銷 中大型專案、現代框架
服務定位器 簡單、漸進式遷移 隱性依賴、反模式風險 舊專案重構過渡期
靜態類別 (無狀態) 簡單、效能好、無實例管理 無法儲存狀態、難以 Mock 工具函數、純函數
單例 (謹慎使用) 簡單、確保唯一實例 上述所有缺點 資源昂貴且真正需唯一 (DB連線、日誌、配置)

DI 容器替代單例:PHP-DI 範例

<?php
// composer require php-di/php-di
use DI\ContainerBuilder;

$container = (new ContainerBuilder())
    ->addDefinitions([
        PDO::class => \DI\factory(function() {
            return new PDO('mysql:host=localhost;dbname=app', 'user', 'pass');
        })->singleton(), // 關鍵:單例範圍
        
        UserRepository::class => \DI\autowire()->constructorParameter('pdo', \DI\get(PDO::class)),
        UserService::class => \DI\autowire(),
    ])
    ->build();

// 使用:容器管理生命週期
$userService = $container->get(UserService::class);
// 同一請求/應用中獲取相同 PDO 實例

// 測試時輕鬆覆寫
$testContainer = (new ContainerBuilder())
    ->addDefinitions([
        PDO::class => $mockPdo,
    ])
    ->build();
?>

何時適合使用單例

✅ 合適場景

  • 資料庫連線池、Redis 連線 (資源昂貴、真正需共享)
  • 配置管理器 (唯一配置來源)
  • 日誌記錄器 (統一輸出通道)
  • 快取管理器 (單一快取實例)
  • 事件分發器 (全域事件總線)

❌ 不合適場景

  • 業務邏輯類別 (UserService、OrderProcessor)
  • 請求相關資料 (當前用戶、Session、Request)
  • 需多實例配置的服務 (多資料庫、多快取)
  • 單元測試目標類別 (難以隔離)
  • 單例模式重構路徑

    1. 識別所有 ::getInstance() 呼叫點
    2. 將依賴改為建構子注入
    3. 在進入點 (Controller、Command) 由容器建構
    4. 移除單例實作代碼 (私有建構子、靜態實例、getInstance)
    5. 配置容器定義 (singleton scope)
    6. 執行測試驗證
    1. 上一篇文章:
    2. 下一篇文章:
    Copyright © 程式碼之家 All Rights Reserved