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

PHP 設計模式:單例模式完整實作與應用場景解析

     編輯:PHP 相關知識

PHP 設計模式:單例模式完整實作與應用場景解析

單例模式 是創建型設計模式之一,確保某個類別在全域僅有一個實例,並提供全域存取點。在 PHP 中常用於資料庫連線、配置管理、日誌記錄器等「重資源、共享狀態」的場景。

一、核心結構三要素

  1. 私有建構子:防止外部 new ClassName() 直接實例化
  2. 靜態私有屬性:儲存唯一實例(static private $instance)
  3. 公開靜態方法:提供全域存取點(慣例命名 getInstance())

補充:實作 __clone() 並拋出錯誤,防止 clone 破壞單一性;PHP 8.1+ 可加上 readonly 屬性強化不可變性。

二、標準實作(PHP 7.4+ 語法)

<?php
declare(strict_types=1);

namespace App\Patterns;

class DatabaseConnection
{
    // 1. 靜態私有屬性:儲存唯一實例
    private static ?self $instance = null;

    // 連線資源(示意)
    private \PDO $pdo;

    // 2. 私有建構子:禁止外部實例化
    private function __construct()
    {
        $dsn = 'mysql:host=127.0.0.1;dbname=app;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,
        ]);
        echo "[單例] 資料庫連線已建立\n";
    }

    // 3. 禁止克隆
    public function __clone(): void
    {
        throw new \LogicException('單例模式禁止克隆');
    }

    // 4. 禁止反序列化(可選)
    public function __wakeup(): void
    {
        throw new \LogicException('單例模式禁止反序列化');
    }

    // 5. 公開靜態存取方法
    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    // 業務方法
    public function query(string $sql, array $params = []): array
    {
        $stmt = $this->pdo->prepare($sql);
        $stmt->execute($params);
        return $stmt->fetchAll();
    }

    public function getPdo(): \PDO
    {
        return $this->pdo;
    }
}

三、使用示範

<?php
require 'vendor/autoload.php';

use App\Patterns\DatabaseConnection;

// 正確用法:透過 getInstance() 取得實例
$db1 = DatabaseConnection::getInstance();
$users = $db1->query('SELECT * FROM users LIMIT 3');
print_r($users);

// 再次呼叫:回傳同一實例
$db2 = DatabaseConnection::getInstance();

// 驗證是否同一物件
var_dump($db1 === $db2); // true

// 錯誤示範(會拋出例外):
// $db3 = new DatabaseConnection();        // Error: 建構子為 private
// $db4 = clone $db1;                      // LogicException: 禁止克隆
// $db5 = unserialize(serialize($db1));    // LogicException: 禁止反序列化

四、進階變體:多鍵單例

依參數建立不同單例(如多資料庫連線):

class MultiDatabase
{
    /** @var self[] */
    private static array $instances = [];

    private \PDO $pdo;

    private function __construct(string $dsn, string $user, string $pass)
    {
        $this->pdo = new \PDO($dsn, $user, $pass);
    }

    public static function getInstance(string $name = 'default'): self
    {
        if (!isset(self::$instances[$name])) {
            // 這裡可從配置檔讀取對應參數
            $config = [
                'default' => ['mysql:host=127.0.0.1;dbname=app', 'user', 'pass'],
                'logs'    => ['mysql:host=127.0.0.1;dbname=logs', 'user', 'pass'],
            ];
            [$dsn, $u, $p] = $config[$name] ?? $config['default'];
            self::$instances[$name] = new self($dsn, $u, $p);
        }
        return self::$instances[$name];
    }
    
    // ... 禁止 clone/wakeup、業務方法同前
}

五、PHP 中的應用場景與優缺點

✅ 適合場景

場景理由
資料庫連線池 / PDO 連線 避免重複建立 TCP 連線、認證握手,減少資源消耗
配置管理 全域唯一配置來源,運行期熱更新友善
日誌記錄器 統一格式、輸出目標,避免多實例競爭檔案鎖
快取客戶端 Redis/Memcached 連線複用,支援連線池
API 客戶端 HTTP 客戶端連線複用、Token 快取

⚠️ 潛在缺點與反模式風險

  • 全域狀態:難以單元測試(無法輕易 Mock/替換),單元測試間相互干擾
  • 隱性相依:類別內部直接呼叫 Singleton::getInstance(),違反依賴倒置原則
  • 擴充性差:繼承單例類別會破壞單一性,需改用多鍵單例或工廠模式
  • 並發問題:PHP 是共享無架構,單例僅在單一請求生命週期內有效,跨請求不共享

💡 現代 PHP 最佳實踐:依賴注入容器管理單例生命週期

與其硬編碼單例,不如交由 DI 容器(如 PHP-DI、Symfony DI、Laravel Service Container)管理:

// 容器設定範例 (PHP-DI / PSR-11)
return [
    \App\Patterns\DatabaseConnection::class => \DI\object()
        ->constructor()  // 單例由容器保證
        ->scope(\DI\Scope::SINGLETON), // 或 SingletonScope
];

類別改為接收介面注入:

class UserService
{
    public function __construct(
        private \App\Contracts\DatabaseInterface $db  // 依賴抽象
    ) {}

    public function getUsers(): array
    {
        return $this->db->query('SELECT * FROM users');
    }
}

六、總結

單例模式在 PHP 中確有其價值(資源重、共享狀態),但濫用會導致強耦合、難測試。現代 PHP 開發建議:

  1. 核心資源類別實作單例邏輯(私有建構子、靜態實例、禁止克隆)
  2. 不要在業務邏輯中直接呼叫 ::getInstance()
  3. 改用 依賴注入容器 管理單例生命週期,類別依賴介面而非具體實作
  4. 單元測試時由容器提供 Mock/替代實作

這樣既保留單例的資源複用優勢,又維持架構的彈性與可測試性。

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