在前面几篇文章中我们讲了创建型设计模式的单例模式,工厂方法,抽象工厂,以及建造者模式,在这篇文章我将为大家来介绍创建型模式的最后一个模式: 原型(Prototype) 模式。
场景
现在我们有一个需求 要求有一个简历类,必须要有姓名,可以设置性别年龄,可以设置工作经历。我们可能需要用到多份简历。
很快我们就写出了第一个版本,代码如下:
简历类 Resume.php
<?php
namespace DesignPatterns\Prototype\V1;
class Resume
{
private $name;
private $sex;
private $age;
private $timeArea;
private $company;
/**
* Resume constructor.
* @param $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
//设置个人信息
public function SetPersonalInfo(string $sex,string $age)
{
$this->sex = $sex;
$this->age = $age;
}
//设置工作经历
public function SetWorkExperience(string $timArea,string $company)
{
$this->timeArea = $timArea;
$this->company = $company;
}
//显示
public function Display(){
return $this->name." ".$this->sex." ".$this->age."<br>工作经历:".$this->timeArea." ".$this->company."<br>";
}
}
运行 client.php:
<?php
namespace DesignPatterns\Prototype\V1;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run(){
$resumea = new Resume("张三");
$resumea->SetPersonalInfo('男','18');
$resumea->SetWorkExperience('2000-2004','xx公司');
$resumeb = new Resume("张三");
$resumeb->SetPersonalInfo('男','18');
$resumeb->SetWorkExperience('2000-2004','xx公司');
$resumec = new Resume("张三");
$resumec->SetPersonalInfo('男','18');
$resumec->SetWorkExperience('2000-2004','xx公司');
echo $resumea->Display();
echo $resumeb->Display();
echo $resumec->Display();
}
}
$client = new Client();
$client->run();
运行结果:
代码没有什么问题,但是试想一下代码中三份简历就需要实例化三次,如果是二十份简历那么我们是不是要实例化二十次,如果我们当中有些输入错了 例如年龄 那么我们就需要改二十次。这对客户端来说是十分糟糕的。这就需要用的我们开头讲的原型模式了。
原型(Prototype)模式
用已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
原型模式的结构
- 原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为 clone克隆的方法。
- 具体原型 (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。
- 客户端 (Client) 可以复制实现了原型接口的任何对象。
使用原型模式实现简历
接下来我们用原型模式去实现简历 PHP的原型模式比较简单, 你只需要去克隆他就好了。代码如下:
原型类 Resume.php
<?php
namespace DesignPatterns\Prototype\V2;
class Resume
{
private $name;
private $sex;
private $age;
private $timeArea;
private $company;
/**
* Resume constructor.
* @param $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
//设置个人信息
public function SetPersonalInfo(string $sex,string $age)
{
$this->sex = $sex;
$this->age = $age;
}
//设置工作经历
public function SetWorkExperience(string $timArea,string $company)
{
$this->timeArea = $timArea;
$this->company = $company;
}
//显示
public function Display(){
return $this->name." ".$this->sex." ".$this->age."<br>工作经历:".$this->timeArea." ".$this->company."<br>";
}
}
运行 Client.php
<?php
namespace DesignPatterns\Prototype\V2;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run(){
$resumea = new Resume("张三");
$resumea->SetPersonalInfo('男','18');
$resumea->SetWorkExperience('2000-2004','xx公司');
$resumeb = clone $resumea;
$resumeb->SetPersonalInfo('男','20');
$resumeb->SetWorkExperience('2000-2004','yy公司');
$resumec = clone $resumea;
$resumec->SetPersonalInfo('男','24');
$resumec->SetWorkExperience('2004-2008','zz公司');
echo $resumea->Display();
echo $resumeb->Display();
echo $resumec->Display();
}
}
$client = new Client();
$client->run();
运行结果:
我们发现使用clone后 我们如果想修改某一份简历,只需要对这一份简历进行修改就可以了,不会影响到其他简历,使用原型模式对对性能也有很大的提高,如果我们使用第一版代码每new
一次,都需要执行一次构造函数,如果构造函数运行时间过长,那么我们多次的执行初始化操作效率就太低了,使用clone后,我们隐藏了对象创建的细节,又极大的提高了性能。
浅复制与深复制
在上面的代码中就是一个原型模式浅复制的例子,clone关键字可以帮我们一个对象的浅副本,克隆出来的产品与原对象有相同的属性。如果对象中有属性是对象,那么它们是不会复制到新产品的对象中的。意思就是如果我们的简历类中如果有对象引用。那么引用的对象数据是不会克隆过来的。
举个例子,我们的简历类中有一个设置工作经验的方法,一般会再有一个工作经历类,当中有时间区间,和公司名称等属性。
然后我们对代码进行改造如下:
工作经历 WorkExperience.php
<?php
namespace DesignPatterns\Prototype\V3;
/**
* 工作经历
* Class WorkExperience
* @package DesignPatterns\Prototype\V3
*/
class WorkExperience
{
private $workDate;
private $company;
/**
* @return mixed
*/
public function getCompany()
{
return $this->company;
}
/**
* @return mixed
*/
public function getWorkDate()
{
return $this->workDate;
}
/**
* @param $workDate
* @return mixed
*/
public function SetWorkDate($workDate)
{
$this->workDate = $workDate;
}
/**
* @param $company
* @return mixed
*/
public function SetCompany($company)
{
$this->company = $company;
}
}
简历类 Resume.php
<?php
namespace DesignPatterns\Prototype\V3;
/**
* 简历类
* Class Resume
* @package DesignPatterns\Prototype\V3
*/
class Resume
{
private $name;
private $sex;
private $age;
private $work;
public function __construct($name)
{
$this->name = $name;
$this->work = new WorkExperience();
}
//设置个人信息
public function SetPersonalInfo(string $sex,string $age)
{
$this->sex = $sex;
$this->age = $age;
}
//设置工作经历
public function SetWorkExperience(string $timArea,string $company)
{
$this->work->SetWorkDate($timArea);
$this->work->SetCompany($company);
}
//显示
public function Display()
{
return $this->name." ".$this->sex." ".$this->age.
"<br>工作经历:".$this->work->getWorkDate()." ".$this->work->getCompany()."<br>";
}
}
运行Client.php
<?php
namespace DesignPatterns\Prototype\V3;
require dirname(__DIR__).'/../vendor/autoload.php';
class Client
{
public function run(){
$resumea = new Resume("张三");
$resumea->SetPersonalInfo('男','18');
$resumea->SetWorkExperience('2000-2004','xx公司');
$resumeb = clone $resumea;
$resumeb->SetPersonalInfo('男','20');
$resumeb->SetWorkExperience('2000-2004','yy公司');
$resumec = clone $resumea;
$resumec->SetPersonalInfo('男','24');
$resumec->SetWorkExperience('2004-2008','zz公司');
echo $resumea->Display();
echo $resumeb->Display();
echo $resumec->Display();
}
}
$client = new Client();
$client->run();
我们可以看到 使用浅复制对于值的引用没什么问题,但对于引用对象还是指向了原来的对象,虽然我们给三份简历都引用设置了工作经历,但可以看到三个引用都是最后一次设置,因为三个引用都指向了同一个对象。
简历的深复制
我们只需要在简历类中创建一个克隆方法__clone()
,在克隆方法中让工作经验克隆完成,然后在对简历的相关字段赋值,最终返回一个深复制的简历对象。
代码如下:
Resume.php
public function __clone()
{
$this->work = clone $this->work;
}
然后我们再次运行 client.php,结果如下
总结
- 什么是原型模式?如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式,来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式,简称原型模式。
- 原型模式的两种实现方法原型模式有两种实现方法,深拷贝和浅拷贝。浅拷贝只会复制对象中基本数据类型数据和引用对象的内存地址,不会递归地复制引用对象,以及引用对象的引用对象……而深拷贝得到的是一份完完全全独立的对象。所以,深拷贝比起浅拷贝来说,更加耗时,更加耗内存空间。
github示例:https://github.com/yangpanyao/design-patterns/tree/master/Prototype
参考书籍: 大话设计模式
评论 (0)