相對於單例模式「全域唯一實例」,多例模式允許同一類擁有多個實例,但針對給定的鍵值組合只存在唯一實例。核心特徵:
getInstance() 提供存取<?php
abstract class Multiton {
private static $instances = array();
public static function getInstance() {
// 以「類名 + 序列化參數」作為唯一鍵
$key = get_called_class() . serialize(func_get_args());
if (!isset(self::$instances[$key])) {
$rc = new ReflectionClass(get_called_class());
self::$instances[$key] = $rc->newInstanceArgs(func_get_args());
}
return self::$instances[$key];
}
}
// 子類繼承 Multiton
class Hello extends Multiton {
public function __construct($string = 'World') {
echo "Hello $string\n";
}
}
class GoodBye extends Multiton {
public function __construct($string = 'my', $string2 = 'darling') {
echo "Goodbye $string $string2\n";
}
}
// 測試驗證
$a = Hello::getInstance('World');
$b = Hello::getInstance('bob'); // 參數不同 → 新實例
// $a !== $b
$c = Hello::getInstance('World'); // 參數相同 → 同一實例
// $a === $c
$d = GoodBye::getInstance();
$e = GoodBye::getInstance(); // 無參數 → 同一實例
// $d === $e
$f = GoodBye::getInstance('your'); // 參數不同 → 新實例
// $d !== $f
?>
$key = get_called_class() . serialize(func_get_args());
get_called_class():晚期靜態綁定,獲取實際調用的子類名func_get_args():獲取所有傳入參數陣列serialize():將參數序列化為字串,確保複雜參數也能作為鍵$rc = new ReflectionClass(get_called_class());
self::$instances[$key] = $rc->newInstanceArgs(func_get_args());
ReflectionClass 動態實例化,支援任意建構子參數newInstanceArgs() 接受參數陣列,完美轉發建構子參數private static $instances 以陣列儲存所有實例,鍵為上述生成的唯一標識,值為物件實例。
| 優勢 | 說明 |
|---|---|
| 記憶體優化 | 相同參數複用實例,避免重複創建 |
| 衝突避免 | 不同參數隔離實例,狀態不互相干擾 |
| 資源池化 | 適用於資料庫連線池、快取物件、配置管理器等 |
| 參數化單例 | 可視為「帶參數的單例」,擴展性極強 |
class DBConnection extends Multiton {
private $pdo;
public function __construct($host, $dbname, $user, $pass) {
$dsn = "mysql:host=$host;dbname=$dbname;charset=utf8";
$this->pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_PERSISTENT => true
]);
}
public function getPDO() { return $this->pdo; }
}
// 不同資料庫配置自動獲取不同連線實例
$mainDB = DBConnection::getInstance('localhost', 'main_db', 'user', 'pass');
$logDB = DBConnection::getInstance('localhost', 'log_db', 'user', 'pass');
$mainDB2 = DBConnection::getInstance('localhost', 'main_db', 'user', 'pass');
// $mainDB === $mainDB2 (同配置複用)
// $mainDB !== $logDB (不同資料庫隔離)
get_called_class() 晚期靜態綁定protected 防止外部直接 new