原型模式(Prototype)
前言
原型模式通过复制现有对象来创建新对象,而非从头开始构建。当对象创建成本较高或需要创建大量相似对象时,原型模式能显著提升性能。本文将详细讲解原型模式的核心原理、深浅拷贝区别及实际应用。
一、核心概念
1.1 定义
用原型实例指定创建对象的种类,并通过拷贝这些原型来创建新对象。
1.2 核心角色
| 角色 | 说明 |
|---|---|
| 原型接口(Prototype) | 声明克隆方法 |
| 具体原型(ConcretePrototype) | 实现克隆方法 |
| 客户端(Client) | 调用克隆方法创建对象 |
二、代码实现
2.1 基础实现
php
<?php
// 原型接口
interface Prototype
{
public function clone(): Prototype;
}
// 具体原型
class ConcretePrototype implements Prototype
{
public int $primitive; // 基本类型
public object $component; // 引用类型
public array $array; // 数组类型
public function __construct(int $primitive, object $component, array $array)
{
$this->primitive = $primitive;
$this->component = $component;
$this->array = $array;
}
// 浅拷贝
public function clone(): Prototype
{
return new ConcretePrototype(
$this->primitive,
(object) (array) $this->component, // 一层浅拷贝
array_values($this->array) // 数组拷贝
);
}
}
// 使用
$original = new ConcretePrototype(100, (object) ['name' => 'Original'], [1, 2, 3]);
$cloned = $original->clone();
var_dump($original->primitive === $cloned->primitive); // true(值相同)
var_dump($original->component === $cloned->component); // false(不同对象)
var_dump($original->array === $cloned->array); // false(不同数组)2.2 深拷贝实现
php
<?php
class DeepPrototype implements Prototype
{
public object $data;
public function __construct(object $data)
{
$this->data = $data;
}
// 深拷贝:递归复制所有嵌套对象
public function clone(): Prototype
{
return new DeepPrototype(
(object) [
'nested' => (object) [
'value' => $this->data->nested->value
]
]
);
}
}
// 使用
$original = new DeepPrototype((object) ['nested' => (object) ['value' => 42]]);
$cloned = $original->clone();
$cloned->data->nested->value = 99;
echo $original->data->nested->value . PHP_EOL; // 42(不影响原对象)2.3 使用 JSON 序列化实现深拷贝
php
<?php
class JsonPrototype implements Prototype
{
public string $name;
public array $items;
public array $config;
public function __construct(string $name, array $items, array $config)
{
$this->name = $name;
$this->items = $items;
$this->config = $config;
}
// JSON 序列化/反序列化实现深拷贝(PHP 中还原为关联数组)
public function clone(): Prototype
{
$data = json_decode(json_encode($this), true);
return new self($data['name'], $data['items'], $data['config']);
}
}2.4 实际案例:文档模板复制
php
<?php
// 文档模板
class Document implements Prototype
{
public string $title;
public string $content;
public string $author;
public array $tags;
public array $metadata;
public function __construct(string $title, string $content, string $author)
{
$this->title = $title;
$this->content = $content;
$this->author = $author;
$this->tags = [];
$this->metadata = ['createdAt' => new DateTime(), 'version' => 1];
}
public function clone(): Prototype
{
$cloned = new Document($this->title, $this->content, $this->author);
$cloned->tags = array_values($this->tags);
$cloned->metadata = [
'createdAt' => clone $this->metadata['createdAt'],
'version' => $this->metadata['version'],
];
return $cloned;
}
public function describe(): string
{
return "{$this->title} by {$this->author} (v{$this->metadata['version']})";
}
}
// 使用:基于模板创建新文档
$template = new Document("项目方案模板", "请在此填写内容...", "系统");
$template->tags = ["模板", "方案"];
// 克隆模板,修改标题和作者
$newDoc = $template->clone();
$newDoc->title = "新项目方案";
$newDoc->author = "Flynn";
$newDoc->metadata['version'] = 2;
echo $template->describe() . PHP_EOL; // 项目方案模板 by 系统 (v1)
echo $newDoc->describe() . PHP_EOL; // 新项目方案 by Flynn (v2)三、深拷贝与浅拷贝
| 对比项 | 浅拷贝 | 深拷贝 |
|---|---|---|
| 基本类型 | 复制值 | 复制值 |
| 引用类型 | 复制引用 | 递归复制所有层 |
| 修改副本影响原对象 | 是(引用类型) | 否 |
| 性能 | 快 | 慢 |
| 实现复杂度 | 低 | 高 |
php
<?php
// 浅拷贝:对象引用共享
$objA = (object) ['a' => 1, 'nested' => (object) ['b' => 2]];
$shallowCopy = clone $objA;
$shallowCopy->nested->b = 99;
echo $objA->nested->b . PHP_EOL; // 99(原对象被修改)
// 深拷贝:完全独立
$objB = (object) ['a' => 1, 'nested' => (object) ['b' => 2]];
$deepCopy = json_decode(json_encode($objB));
$deepCopy->nested->b = 99;
echo $objB->nested->b . PHP_EOL; // 2(原对象不受影响)四、适用场景
| 场景 | 说明 |
|---|---|
| 对象创建成本高 | 数据库查询、复杂计算结果 |
| 大量相似对象 | 避免重复初始化 |
| 保存对象快照 | 撤销/重做功能 |
| 模板复制 | 基于模板创建新对象 |
| 避免构造函数副作用 | 跳过初始化逻辑 |
五、优缺点分析
| 优点 | 缺点 |
|---|---|
| 减少对象创建成本 | 深拷贝实现复杂 |
| 简化对象创建 | 循环引用处理困难 |
| 提高性能 | 克隆方法需要维护 |
| 避免重复初始化 | JSON 方式丢失方法和类 |
六、常见踩坑与问题排查
6.1 循环引用
php
<?php
// 问题:循环引用导致 JSON 深拷贝失败
$obj = new stdClass();
$obj->name = "test";
$obj->self = $obj; // 循环引用
json_decode(json_encode($obj)); // 失败或返回 null
// 解决:使用递归克隆,记录已访问对象
function deepClone($obj, array &$visited = []): mixed
{
if (!is_object($obj) && !is_array($obj)) {
return $obj;
}
$hash = spl_object_id($obj);
if (isset($visited[$hash])) {
return $visited[$hash];
}
if (is_array($obj)) {
$cloned = [];
foreach ($obj as $key => $value) {
$cloned[$key] = deepClone($value, $visited);
}
return $cloned;
}
$cloned = clone $obj;
$visited[$hash] = $cloned;
foreach (get_object_vars($cloned) as $key => $value) {
$cloned->$key = deepClone($value, $visited);
}
return $cloned;
}6.2 丢失类信息
php
<?php
// 问题:JSON 方式丢失对象类信息
class Person
{
public string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function greet(): string
{
return "Hello, {$this->name}";
}
}
$original = new Person("Flynn");
$cloned = json_decode(json_encode($original)); // 转为 stdClass
// $cloned->greet(); // 报错:greet 不存在
// 解决:使用 clone 关键字或手动构造
$clonedPerson = clone $original;
echo $clonedPerson->greet() . PHP_EOL; // Hello, Flynn6.3 特殊类型丢失
JSON 序列化会丢失 DateTime、SplObjectStorage、闭包等特殊类型。
php
<?php
// 解决:自定义 __clone 魔术方法
class DocumentWithDate
{
public DateTime $createdAt;
public function __construct()
{
$this->createdAt = new DateTime();
}
public function __clone()
{
// 克隆时显式复制 DateTime 对象
$this->createdAt = clone $this->createdAt;
}
}
$original = new DocumentWithDate();
$cloned = clone $original;七、优化方案与进阶
7.1 原型注册表
php
<?php
class PrototypeRegistry
{
private array $prototypes = [];
public function register(string $key, Prototype $prototype): void
{
$this->prototypes[$key] = $prototype;
}
public function clone(string $key): ?Prototype
{
return $this->prototypes[$key]?->clone();
}
}
$registry = new PrototypeRegistry();
$registry->register("document", new Document("模板", "内容", "系统"));
$newDoc = $registry->clone("document");7.2 结合工厂模式
php
<?php
class PrototypeFactory
{
private array $prototypes = [];
public function register(string $key, Prototype $prototype): void
{
$this->prototypes[$key] = $prototype;
}
public function create(string $key): Prototype
{
if (!isset($this->prototypes[$key])) {
throw new RuntimeException("Unknown prototype: {$key}");
}
return $this->prototypes[$key]->clone();
}
}八、全文总结
原型模式的核心是 通过复制现有对象来创建新对象,避免重复初始化。
核心要点:
- 浅拷贝只复制第一层,引用类型共享
- 深拷贝递归复制所有层,完全独立
- JSON 序列化简单但有局限(丢失方法、类信息、特殊类型)
- 循环引用需要特殊处理
- 可与注册表、工厂模式结合使用
