深入了解对象克隆
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 61 课,共 91 课。
你在本课程前面学习过 __clone 魔术方法。现在我们来了解一下,当对象的属性中包含其他对象时,浅克隆和深克隆之间的关键区别。
默认情况下,PHP 在克隆时会执行浅拷贝。标量值会被复制,但对象属性仍然是对同一对象的引用:
<?php
class Address {
public function __construct(public string $city) {}
}
class Person {
public function __construct(
public string $name,
public Address $address
) {}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->name = "Bob";
$clone->address->city = "London";
echo $original->name . " - " . $original->address->city;
输出:
Alice - London注意,修改 $clone->name 没有影响原对象,但修改地址中的城市却影响了原对象!这两个对象共享同一个 Address 实例。
要创建一个深拷贝,并同时克隆嵌套对象,请实现 __clone:
<?php
class Person {
public function __construct(
public string $name,
public Address $address
) {}
public function __clone(): void {
$this->address = clone $this->address;
}
}
$original = new Person("Alice", new Address("Paris"));
$clone = clone $original;
$clone->address->city = "London";
echo $original->address->city;
输出:
Paris现在,每个 Person 都有自己独立的 Address。当对象中包含多个嵌套对象时,你必须在 __clone 中克隆每个对象,才能确保原对象和副本真正相互独立。
挑战
简单我们来构建一个文档管理系统,展示浅克隆和深度克隆之间的区别。你将创建包含作者信息的文档,并确保克隆文档时会创建一个真正独立的副本。
你将把代码分成 3 个文件:
Author.php:创建一个表示文档作者的Author类。使用构造函数属性提升定义一个 public$name(string)和一个 public$email(string)。Document.php:创建一个包含作者的Document类。引入 Author 文件。这个类应该:- 使用构造函数属性提升定义一个 public
$title(string)和一个 public$author(Author) - 实现
__clone()魔术方法来执行深度克隆。克隆文档时,也应该克隆它的作者,这样修改克隆文档的作者就不会影响原文档 - 包含一个名为
getInfo()的方法,返回"[title] by [author name] ([author email])"
- 使用构造函数属性提升定义一个 public
main.php:引入 Document 文件。你会收到 3 个输入:文档标题、作者姓名和作者邮箱。使用提供的输入创建一个包含
Author的Document。克隆该文档,然后在克隆文档的作者姓名后追加" (Copy)",并在克隆文档的标题前添加"Copy of "。打印两行:
- 原文档的信息
- 克隆文档的信息
如果深度克隆实现正确,修改克隆文档的作者后,原文档的作者应该保持不变。
这个挑战展示了深度克隆为何重要。如果没有正确实现 __clone(),两个文档就会共享同一个 Author 对象,修改其中一个会影响另一个。
亲手试试
<?php
require_once 'Document.php';
// 读取输入
$title = trim(fgets(STDIN));
$authorName = trim(fgets(STDIN));
$authorEmail = trim(fgets(STDIN));
// TODO: 使用提供的姓名和邮箱创建一个 Author 对象
// TODO: 使用提供的标题和 Author 对象创建一个 Document 对象
// TODO: 克隆文档
// TODO: 在克隆对象的作者姓名后追加 " (Copy)"
// TODO: 在克隆对象的标题前添加 "Copy of "
// TODO: 打印原始文档的信息
// TODO: 打印克隆文档的信息
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器