一、定义
原型模式(Prototype Pattern)是一种对象创建型模式,用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
原型模式的基本原理是通过将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象复制原型来实现创建过程。
二、模式结构
原型模式包含如下角色:
1.Prototype(抽象原型类)
抽象原型类是定义具有克隆自己的方法的接口,是所有具体原型类的公共父类,可以是抽象类,也可以是接口。
2.ConcretePrototype(具体原型类)
具体原型类实现具体的克隆方法,在克隆方法中返回自己的一个克隆对象。
3.Client(客户类)
客户类让一个原型克隆自身,从而创建一个新的对象。在客户类中只需要直接实例化或通过工厂方法等方式创建一个对象,再通过调用该对象的克隆方法复制得到多个相同的对象。
三、深克隆与浅克隆
通常情况下,一个类包含一些成员对象。在使用原型模式克隆对象时,根据其成员对象是否也克隆,原型模式可以分为两种形式:深克隆和浅克隆。
1.浅克隆
在浅克隆中,被复制对象的所有普通成员变量都具有与原来的对象相同的值,而所有对其他对象的引用仍然指向原来的对象。换言之,浅克隆仅仅复制所考虑的对象,而不复制它所引用的成员对象,也就是其中的成员对象并不复制。
其中,obj1为原型对象,obj2为复制后的对象,containedObj1和containedObj2为成员对象
2.深克隆
在深克隆中,被复制对象的所有普通成员变量也都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深克隆把要复制的对象所引用的对象都复制了一遍。
四、优缺点
1.优点:
(1)可以简化对象的创建过程,通过一个已有实例可以提高新实例的创建效率
(2)可以动态增加或减少产品类
(3)提供了简化的创建结构
(4)可以使用深克隆的方式保存对象的状态
2.缺点:
(1)违背了“开闭原则”
(2)在实现深克隆时需要编写较为复杂的代码
五、试用环境
(1)创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其属性稍作修改
(2)如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占内存不大的时候,也可以使用原型模式配合备忘录模式
(3)需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
六、实例:
在使用某OA系统时,有些岗位的员工发现他们每周的工作都大同小异,因此在填写工作周报时很多内容都是重复的,为了提高工作周报的创建效率,大家迫切地希望有一种机制能够快速创建相同或者相似的周报,包括创建周报的附件。请使用原型模式对该OA系统中的工作周报创建模块进行改进。
类图(感觉不太会画。。):
附件类 Attachment.php:
<?php
class Attachment
{
private $_name;
function __set($name, $value)
{
// TODO: Implement __set() method.
$prop="_$name";
$this->$prop=$value;
}
function __get($name)
{
// TODO: Implement __get() method.
return $this->_name;
}
function download(){
$file=fopen("$this->_name.txt","a")or die("Unable to open file!");
$txt="name:$this->_name;
content:我是附件;";
fwrite($file, $txt);
fclose($file);
return "已下载".$this->_name.'.text'.PHP_EOL;
}
}
周报类 WeeklyLog.php:
<?php
class WeeklyLog
{
private $_name;
private $_date;
private $_content;
private $_attachment;
function __get($name)
{
$prop="_$name";
return $this->$prop;
}
function __set($name, $value)
{
$prop="_$name";
$this->$prop=$value;
}
//深克隆
function __clone()
{
$this->_attachment = clone $this->_attachment;
// TODO: Implement __clone() method.
}
function __toString()
{
// TODO: Implement __toString() method.
return '名字:'.$this->_name.PHP_EOL.'时间:'.$this->_date.PHP_EOL.'内容:'.$this->_content.PHP_EOL.'附件名:'.$this->_attachment->name.PHP_EOL;
}
}
客户端 client.php:
<?php
require_once "Attachment.php";
require_once "WeeklyLog.php";
class client{
public static function main(){
$weeklyLog=new WeeklyLog();
$weeklyLog->name='模板名字';
$weeklyLog->content='模板内容';
$weeklyLog->date='2000-5-1';
$weeklyLog->attachment=new Attachment();
$weeklyLog->attachment->name="模板附件";
echo "是否要使用周报模板?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");//调试时输入
$lan = trim(fgets(STDIN));//输入的字符
if($lan=='Y'){
$weeklyLogNew=clone $weeklyLog;
$weeklyLogNew->date=date('Y-m-d H:i:s');
echo "是否要更改周报名字?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");
$lname = trim(fgets(STDIN));//输入的字符
if($lname=='Y'){
fwrite(STDOUT, "请输入新的周报名称,换行结束 ");
$name = trim(fgets(STDIN));//输入的字符
$weeklyLogNew->name=$name;
}
echo "是否要更改周报内容?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");
$lcontent = trim(fgets(STDIN));//输入的字符
if($lcontent=='Y'){
fwrite(STDOUT, "请输入新的周报内容,换行结束 ");
$content = trim(fgets(STDIN));//输入的字符
$weeklyLogNew->content=$content;
}
echo "是否要引用模板附件?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");
$l = trim(fgets(STDIN));//输入的字符
if($l=='Y'){
$weeklyLogNew->attachment=new Attachment();
echo "是否要更改附件名字?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");
$aname = trim(fgets(STDIN));//输入的字符
if($aname=='Y'){
fwrite(STDOUT, "请输入新的附件名字,换行结束 ");
$alname = trim(fgets(STDIN));//输入的字符
$weeklyLogNew->attachment->name=$alname;
echo "是否要下载附件?";
fwrite(STDOUT, "请输入‘Y’or‘N’,换行结束 ");
$ldownload = trim(fgets(STDIN));//输入的字符
if($ldownload=='Y'){
echo $weeklyLogNew->attachment->download();
}
}
}
echo $weeklyLogNew;
}
}
}
client::main();
客户端稍微写的有一点复杂,做了一些询问。反正看看吧
效果(在phpstorm运行):
出现了一个叫ATT.txt的附件