當需要大量建立「大部分相同、小部分不同」的物件時,每次 new 完整初始化開銷過大。**原型模式** 讓我們先建立一個「原型物件」,後續透過 clone 複製並只初始化差異部分,大幅提升效能。本文結合 Zend Framework 2 原始碼與經典教學範例,完整解析 PHP 實作要點。
| 角色 | 職責 | PHP 對應 |
|---|---|---|
| 抽象原型 | 宣告克隆介面 | 介面或抽象類別 |
| 具體原型 | 實作克隆邏輯 | 實作 __clone() 的類別 |
| 客戶端 | 請求克隆 | 使用 clone 關鍵字 |
痛點: 建構子若包含昂貴操作(資料庫連線、大量記憶體分配),每次 new 都會重複執行。
解法:
__construct():只做**共用、昂貴、不變**的初始化(如 DB Adapter、TableGateway 設定)initialize($data):做**每個實例獨有**的輕量初始化(如當筆資料行)Module.php 服務工廠:
public function getServiceConfig() {
return [
'factories' => [
'Album\Model\AlbumTable' => function ($sm) {
$tableGateway = $sm->get('AlbumTableGateway');
return new AlbumTable($tableGateway);
},
'AlbumTableGateway' => function ($sm) {
$dbAdapter = $sm->get('Zend\Db\Adapter\Adapter');
// 原型:空的 ResultSet + 空的 Album 實體原型
$resultSetPrototype = new ResultSet();
$resultSetPrototype->setArrayObjectPrototype(new Album());
return new TableGateway('album', $dbAdapter, null, $resultSetPrototype);
},
],
];
}
流程:
TableGateway::select() 執行查詢clone $this->resultSetPrototype 取得新 ResultSet$resultSet->initialize($rowData) 填入資料// 共用資料庫適配器(昂貴資源)
class DbAdapter {
public function fetchAllFromTable(string $table): array {
// 模擬查詢回傳多行陣列
return [['id'=>1,'name'=>'Alice'], ['id'=>2,'name'=>'Bob']];
}
}
// 具體原型:RowGateway
class RowGateway {
private DbAdapter $dbAdapter;
private string $tableName;
public array $data = [];
public function __construct(DbAdapter $dbAdapter, string $tableName) {
$this->dbAdapter = $dbAdapter;
$this->tableName = $tableName;
// 昂貴操作只在原型建立時做一次
}
public function initialize(array $rowData): void {
$this->data = $rowData; // 輕量:每行資料不同
}
public function save(): void { /* ... */ }
public function delete(): void { /* ... */ }
public function refresh(): void { /* ... */ }
}
// 使用原型的 Repository
class UserRepository {
private DbAdapter $dbAdapter;
private RowGateway $rowGatewayPrototype;
public function __construct(DbAdapter $dbAdapter, RowGateway $prototype = null) {
$this->dbAdapter = $dbAdapter;
$this->rowGatewayPrototype = $prototype ?? new RowGateway($dbAdapter, 'user');
}
public function getUsers(): array {
$rows = [];
foreach ($this->dbAdapter->fetchAllFromTable('user') as $rowData) {
$row = clone $this->rowGatewayPrototype; // 關鍵:clone 原型
$row->initialize($rowData); // 初始化差異資料
$rows[] = $row;
}
return $rows;
}
}
// 使用示範
$db = new DbAdapter();
$repo = new UserRepository($db);
$users = $repo->getUsers(); // 內部自動 clone + initialize
foreach ($users as $user) {
echo $user->data['name'] . PHP_EOL;
}
class ReadWriteRowGateway extends RowGateway {
private DbAdapter $readDbAdapter;
public function __construct(DbAdapter $read, DbAdapter $write, string $table) {
$this->readDbAdapter = $read;
parent::__construct($write, $table); // 寫入用主庫
}
public function refresh(): void {
// 讀取用從庫
$data = $this->readDbAdapter->fetchOne($this->tableName, $this->data['id']);
$this->initialize($data);
}
}
// 注入擴充原型
$repo = new UserRepository(
$writeDb,
new ReadWriteRowGateway($readDb, $writeDb, 'user')
);
| 模式 | 適用場景 | PHP 典型用法 |
|---|---|---|
| 原型 | 大量相似物件、初始化昂貴 | clone + initialize() |
| 工廠方法 | 依條件建立不同子類別 | Factory::create($type) |
| 建造者 | 複雜物件分步建構 | $builder->setX()->setY()->build() |
| 單例 | 全域唯一實例 | static getInstance() |
clone 預設為**淺拷貝**,包含資源/物件屬性時需實作 __clone() 深拷貝readonly 屬性確保不可變性原型模式在 PHP 中的精髓:**建構子做重活、initialize 做輕活、clone 做複製**。Zend Framework 的 ResultSetPrototype 是教科書級實作,掌握此模式可顯著優化大量物件建立場景(報表生成、批次匯出、多行資料映射)的效能與記憶體表現。