Menu
CoddyTech

深入了解对象克隆

本课属于 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 中克隆每个对象,才能确保原对象和副本真正相互独立。

challenge icon

挑战

简单

我们来构建一个文档管理系统,展示浅克隆和深度克隆之间的区别。你将创建包含作者信息的文档,并确保克隆文档时会创建一个真正独立的副本。

你将把代码分成 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])"
  • 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: 打印克隆文档的信息

?>
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: PHP 在线编译器