
PHP 原型模式讲解和代码示例
原型是一种创建型设计模式, 使你能够复制对象, 甚至是复杂对象, 而又无需使代码依赖它们所属的类。
所有的原型类都必须有一个通用的接口, 使得即使在对象所属的具体类未知的情况下也能复制对象。 原型对象可以生成自身的完整副本, 因为相同类的对象可以相互访问对方的私有成员变量。
复杂度:
流行度:
使用示例: PHP 中提供立即可用的原型模式。 你可以使用 clone
关键字创建一个对象的完整副本。 如果想要某个类支持克隆功能, 你需要实现 __clone
方法。
识别方法: 原型可以简单地通过 clone
或 copy
等方法来识别。
概念示例
本例说明了原型设计模式的结构并重点回答了下面的问题:
- 它由哪些类组成?
- 这些类扮演了哪些角色?
- 模式中的各个元素会以何种方式相互关联?
了解该模式的结构后, 你可以更容易地理解下面基于真实世界的 PHP 应用案例。
index.php: 概念示例
<?php
namespace RefactoringGuru\Prototype\Conceptual;
/**
* The example class that has cloning ability. We'll see how the values of field
* with different types will be cloned.
*/
class Prototype
{
public $primitive;
public $component;
public $circularReference;
/**
* PHP has built-in cloning support. You can `clone` an object without
* defining any special methods as long as it has fields of primitive types.
* Fields containing objects retain their references in a cloned object.
* Therefore, in some cases, you might want to clone those referenced
* objects as well. You can do this in a special `__clone()` method.
*/
public function __clone()
{
$this->component = clone $this->component;
// Cloning an object that has a nested object with backreference
// requires special treatment. After the cloning is completed, the
// nested object should point to the cloned object, instead of the
// original object.
$this->circularReference = clone $this->circularReference;
$this->circularReference->prototype = $this;
}
}
class ComponentWithBackReference
{
public $prototype;
/**
* Note that the constructor won't be executed during cloning. If you have
* complex logic inside the constructor, you may need to execute it in the
* `__clone` method as well.
*/
public function __construct(Prototype $prototype)
{
$this->prototype = $prototype;
}
}
/**
* The client code.
*/
function clientCode()
{
$p1 = new Prototype();
$p1->primitive = 245;
$p1->component = new \DateTime();
$p1->circularReference = new ComponentWithBackReference($p1);
$p2 = clone $p1;
if ($p1->primitive === $p2->primitive) {
echo "Primitive field values have been carried over to a clone. Yay!\n";
} else {
echo "Primitive field values have not been copied. Booo!\n";
}
if ($p1->component === $p2->component) {
echo "Simple component has not been cloned. Booo!\n";
} else {
echo "Simple component has been cloned. Yay!\n";
}
if ($p1->circularReference === $p2->circularReference) {
echo "Component with back reference has not been cloned. Booo!\n";
} else {
echo "Component with back reference has been cloned. Yay!\n";
}
if ($p1->circularReference->prototype === $p2->circularReference->prototype) {
echo "Component with back reference is linked to original object. Booo!\n";
} else {
echo "Component with back reference is linked to the clone. Yay!\n";
}
}
clientCode();
Output.txt: 执行结果
Primitive field values have been carried over to a clone. Yay!
Simple component has been cloned. Yay!
Component with back reference has been cloned. Yay!
Component with back reference is linked to the clone. Yay!
真实世界示例
原型模式提供了一种复制已有对象的简便方式, 可代替直接复制对象的所有成员变量来对对象进行重构的方法。 直接复制的方式不仅让你与被克隆类所属的对象相耦合, 还不允许你复制私有成员变量的内容。 原型模式让你能够在被克隆类的内部进行克隆工作, 因此可以不受限制地访问类的私有成员变量。
本例说明了如何使用原型模式克隆复杂的页面对象。 页面类拥有许多私有成员变量, 但它们都通过原型模式被复制到了克隆对象中。
index.php: 真实世界示例
<?php
namespace RefactoringGuru\Prototype\RealWorld;
/**
* Prototype.
*/
class Page
{
private $title;
private $body;
/**
* @var Author
*/
private $author;
private $comments = [];
/**
* @var \DateTime
*/
private $date;
// +100 private fields.
public function __construct(string $title, string $body, Author $author)
{
$this->title = $title;
$this->body = $body;
$this->author = $author;
$this->author->addToPage($this);
$this->date = new \DateTime();
}
public function addComment(string $comment): void
{
$this->comments[] = $comment;
}
/**
* You can control what data you want to carry over to the cloned object.
*
* For instance, when a page is cloned:
* - It gets a new "Copy of ..." title.
* - The author of the page remains the same. Therefore we leave the
* reference to the existing object while adding the cloned page to the list
* of the author's pages.
* - We don't carry over the comments from the old page.
* - We also attach a new date object to the page.
*/
public function __clone()
{
$this->title = "Copy of " . $this->title;
$this->author->addToPage($this);
$this->comments = [];
$this->date = new \DateTime();
}
}
class Author
{
private $name;
/**
* @var Page[]
*/
private $pages = [];
public function __construct(string $name)
{
$this->name = $name;
}
public function addToPage(Page $page): void
{
$this->pages[] = $page;
}
}
/**
* The client code.
*/
function clientCode()
{
$author = new Author("John Smith");
$page = new Page("Tip of the day", "Keep calm and carry on.", $author);
// ...
$page->addComment("Nice tip, thanks!");
// ...
$draft = clone $page;
echo "Dump of the clone. Note that the author is now referencing two objects.\n\n";
print_r($draft);
}
clientCode();
Output.txt: 执行结果
Dump of the clone. Note that the author is now referencing two objects.
RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Copy of Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
(
[name:RefactoringGuru\Prototype\RealWorld\Author:private] => John Smith
[pages:RefactoringGuru\Prototype\RealWorld\Author:private] => Array
(
[0] => RefactoringGuru\Prototype\RealWorld\Page Object
(
[title:RefactoringGuru\Prototype\RealWorld\Page:private] => Tip of the day
[body:RefactoringGuru\Prototype\RealWorld\Page:private] => Keep calm and carry on.
[author:RefactoringGuru\Prototype\RealWorld\Page:private] => RefactoringGuru\Prototype\RealWorld\Author Object
*RECURSION*
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
[0] => Nice tip, thanks!
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306237
[timezone_type] => 3
[timezone] => UTC
)
)
[1] => RefactoringGuru\Prototype\RealWorld\Page Object
*RECURSION*
)
)
[comments:RefactoringGuru\Prototype\RealWorld\Page:private] => Array
(
)
[date:RefactoringGuru\Prototype\RealWorld\Page:private] => DateTime Object
(
[date] => 2018-06-04 14:50:39.306272
[timezone_type] => 3
[timezone] => UTC
)
)