[TOC] #### 1. 原型模式 --- 原型模式(Prototype Pattern)是一种创建型设计模式,它通过复制现有的实例来创建新的对象,而不是通过实例化类 它的核心思想是: > 不通过 `new` 创建对象,而是通过复制(克隆)已有对象来创建新对象 PHP 天然支持原型模式,因为提供了 `clone` 关键字以及 `__clone` 魔术方法 原型模式的核心就一句话:已有对象作为模版,通过 clone 快速创建新对象 ```php class Demo { public $object; public function __clone() { // 深拷贝处理 $this->object = clone $this->object; } } $demo1 = new Demo(); $demo1->object = new stdClass(); $demo1->object->value = '你好'; $demo2 = clone $demo1; $demo2->object->value = 'Hello'; echo $demo1->object->value; // 你好 ``` #### 2. 为什么需要原型模式 --- 在实际开发中,有些对象的创建过程可能比较复杂,遇到以下几种情况使用原型模式非常合适: + 对象创建成本高昂:构造函数需要读取数据库、加载复杂配置或调用第三方 API(直接 `new` 会消耗大量资源) + 需要创建多个相似对象:新对象与现有对象只有少量属性不同,克隆后只需修改差异部分即可 ```php class User { public function __construct() { echo "初始化用户..." . date('Y-m-d H:i:s') . "\n"; // 模拟复杂初始化 sleep(2); } } // 创建对象(每次 new 都要重复执行复杂逻辑) // $user1 = new User(); // $user2 = new User(); // $user3 = new User(); // 此时可以使用原型模式,这样只初始化一次,后续对象通过复制获得(避免重复执行复杂逻辑) $user1 = new User(); $user2 = clone $user1; $user3 = clone $user1; ``` #### 3. 浅拷贝与深拷贝(重点解析) --- PHP 的 `clone` 默认执行的是浅拷贝,这是使用 PHP 原型模式时最需要注意的地方 + 浅拷贝:只复制对象本身的标量属性(如 int/string),对于对象内部的引用类型,只会复制其引用(内存地址) + 这意味着修改克隆体中的引用对象,会影响原型中的对应对象 + 深拷贝:不仅复制对象本身,还会递归地复制其内部引用的所有对象,确保新旧对象完全独立 简单来说,PHP 的 `clone` 关键字是浅拷贝: + 普通属性复制值 + 对象属性复制引用 ```php class Profile { public $address; } class User { public $name; public $profile; public $likes = ['html']; } $user1 = new User(); $user1->name = "张三"; // 这里需要特别注意,profile 是一个对象(user1 和 user2 的 profile 将指向同一个对象) $user1->profile = new Profile(); $user1->profile->address = "北京"; $user2 = clone $user1; $user2->name = "李四"; $user2->profile->address = "上海"; echo $user1->name; // 张三 echo $user1->profile->address; // 上海 array_push($user2->likes, 'css'); print_r($user1->likes); // ["html"] print_r($user2->likes); // ["html", "css"] ``` 如何实现深拷贝 ? 可以在 `__clone` 方法中手动对内部的引用对象再次执行 `clone`,或利用序列化和反序列化来实现彻底的深拷贝 方式一:在 `__clone` 中手动克隆子对象(推荐) ```php class Profile { public $address; } class User { public $profile; public function __clone() { // 手动克隆内部的子对象,切断引用关联 $this->profile = clone $this->profile; } } $user1 = new User(); $user1->profile = new Profile(); $user1->profile->address = "北京"; $user2 = clone $user1; $user2->profile->address = "上海"; echo $user1->profile->address; // 北京 echo $user2->profile->address; // 上海 ``` 方式二:使用序列化(适用于极其复杂的嵌套结构) ```php public function __clone() { // 将对象序列化为字符串再反序列化回来,从而生成全新的独立对象 $this->profile = unserialize(serialize($this->profile)); } ``` #### 4. 实际应用场景(游戏角色工厂) --- 假设我们要开发一个游戏,战士和法师都有共同的初始化逻辑(如加载模型、绑定动画),但每次创建都要设置不同的名字 ```php // 抽象角色类 abstract class Character { public $name; public $model; public $skills = []; abstract public function __clone(); } // 具体角色:战士 class Warrior extends Character { public function __construct() { // 模拟耗时的初始化操作 $this->model = "加载重型铠甲模型..."; $this->skills = ["普通攻击", "重击"]; } public function __clone() { // 确保技能数组是独立的(虽然数组在PHP中默认按值传递,但如果是对象数组则需深拷贝) $this->name = ""; // 重置名字,由外部赋予 } } // 角色工厂 class CharacterFactory { private $warriorPrototype; public function __construct() { // 预先创建一个初始化好的原型对象 $this->warriorPrototype = new Warrior(); } public function createWarrior(string $name): Warrior { $warrior = clone $this->warriorPrototype; // 快速克隆 $warrior->name = $name; // 仅修改差异属性 return $warrior; } } // 客户端使用 $factory = new CharacterFactory(); $hero1 = $factory->createWarrior("亚瑟"); $hero2 = $factory->createWarrior("吕布"); echo $hero1->name . " 的技能: " . implode(", ", $hero1->skills) . "\n"; echo $hero2->name . " 的技能: " . implode(", ", $hero2->skills) . "\n"; ``` #### 5. 面试常见问题 --- ##### PHP 原型模式是什么 ? 原型模式是一种创建型设计模式,通过 `clone` 关键字克隆已有对象创建新对象,而不是使用 `new` 关键字实例化 ##### clone 默认是浅拷贝还是深拷贝 ? PHP 的 `clone` 默认是浅拷贝,也就是:普通属性复制值、对象属性复制引用 ##### 如何实现深拷贝 ? 在 `__clone` 方法中手动对内部引用的对象再次执行 `clone`,如果是复杂的嵌套结构可以使用序列化和反序列化 ##### 哪些场景适合原型模式 ? 常见场景: + Query Builder(查询构造器) + 配置对象 + 表单对象 + 模板对象 + 缓存对象 + 大量相似对象场景