Skip to content

原型模式(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, Flynn

6.3 特殊类型丢失

JSON 序列化会丢失 DateTimeSplObjectStorage、闭包等特殊类型。

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();
    }
}

八、全文总结

原型模式的核心是 通过复制现有对象来创建新对象,避免重复初始化

核心要点

  1. 浅拷贝只复制第一层,引用类型共享
  2. 深拷贝递归复制所有层,完全独立
  3. JSON 序列化简单但有局限(丢失方法、类信息、特殊类型)
  4. 循环引用需要特殊处理
  5. 可与注册表、工厂模式结合使用