原型设计模式(Prototype Design Pattern)很有意思, 因为它使用了一种克隆技术来复制实例化的对象. 新对象是通过复制原型实例来创建的. 在这里, 实例是批实例化的具体类.原型设计模式的目的是通过使用克隆来减少实例化对象的开销.与其从一个类实例化新对象, 完全可以使用一个已有实例的克隆.
克隆函数
PHP中使用原型设计模式的关键是要了解如何使用内置函数__clone().
<?php
abstract class CloneObject
{
public $name;
public $picture;
abstract function __clone();
}
class Boy extends CloneObject
{
public function __construct()
{
$this->face = "handsome";
$this->name = "chenqionghe";
}
public function display()
{
echo 'look : '.$this->face;;
echo '<br />'.$this->name.'<br />';
}
public function __clone() {}
}
$boy = new Boy();
$boy->display();
$cloneBoy = clone $boy;
$cloneBoy->face = "still handsome";
$cloneBoy->display();
运行结果如下
look : handsome
chenqionghe
look : still handsome
chenqionghe
$cloneBoy实例是通过克隆Boy的实例$boy, 它可以像$boy一样访问相同的属性, 而且像Boy类的直接实例一样改变这些属性.
注意: 对于所克隆的实例 , clone关键字会为该实例的类实例化另一个实例(使用克隆关键字可以创建一个类的副本, 如果可能, 会自动调用对象的__clone方法, 但不能直接调用 对象的__clone方法), 关于过程, 有一点需要注意的是, 克隆不会不会启动构造函数中的动作.
简单的原型例子
我们以研究果蝇为例.研究的目标是建立一个原型果蝇, 然后一旦出现变异, 就构建这个变异果蝇
抽象类接口和具体实现
原型(IPrototype)的两个具体类实现分别表示不同性别的果蝇, 包括性别变量(gender)和不同性别的和行为.
IPrototype.php
<?php
abstract class IPrototype
{
public $eyeColor;
public $winBeat;
public $unitEypes;
abstract function __clone();
}
IPrototype的这两个实现的区别体现在性别上, 性别用常量标识, 一个是MALE,另一个是FEMAIL.雄果蝇有一个$mated布尔变量, 雄果蝇交配后,这个布尔变量会设置为true, 雌果蝇有一个$fecundity变量,其中包含一个数字值, 表示这只雄果蝇的繁殖能力(产卵个数):
MaleProto.php
<?php
include_once('IPrototype.php');
class MaleProto extends IPrototype
{
const gender = "MALE";
public $mated;
public function __construct()
{
$this->eyeColor = "red";
$this->winBeat = "220";
$this->unitEypes = "760";
}
public function __clone(){}
}
FemaleProto.php
<?php
include_once('IPrototype.php');
class FemaleProto extends IPrototype
{
const gender = "FEMAIL";
public $fecundity;
public function __construct()
{
$this->eyeColor = "red";
$this->winBeat = "220";
$this->unitEypes = "760";
}
public function __clone(){}
}
客户
在原型设计模式中,Clien类确实是一个不可缺少的部分.原因在于, 尽管要将子类具体实现作为实例的模板,但使用相同的模板克隆实例的具体工作是由Client类完成的
Client.php
<?php
function __autoload($class_name)
{
include_once $class_name.'.php';
}
class Client
{
//用于直接实例化
private $fly1;
private $fly2;
//用于克隆
private $c1Fly;
private $c2Fly;
private $updatedCloneFly;
public function __construct()
{
//实例化
$this->fly1 = new MaleProto();
$this->fly2 = new FemaleProto();
//克隆
$this->c1Fly = clone $this->fly1;
$this->c2Fly = clone $this->fly2;
$this->updatedCloneFly = clone $this->fly2;
//更新克隆
$this->c1Fly->mated = "true";
$this->c2Fly->fecundity = '186';
$this->updatedCloneFly->eyeColor = "purple";
$this->updatedCloneFly->winBeat = "220";
$this->updatedCloneFly->unitEyes = '750';
$this->updatedCloneFly->fecundity = '92';
//通过类型提示方法发送
$this->showFly($this->c1Fly);
$this->showFly($this->c2Fly);
$this->showFly($this->updatedCloneFly);
}
private function showFly(IPrototype $fly)
{
echo "Eye color: ".$fly->eyeColor."<br />";
echo "Wing Beats/second: ".$fly->winBeat."<br />";
echo "Eye units: ".$fly->unitEypes."<br />";
$genderNow = $fly::gender;
echo "Gender: ".$genderNow."<br />";
if($genderNow == "FEMAIL")
{
echo "Number of eggs: ".$fly->fecundity."<hr />";
}
else
{
echo "Mated: ".$fly->mated."<hr />";
}
}
}
$worker = new Client();
运行结果如下
Eye color: red
Wing Beats/second: 220
Eye units: 760
Gender: MALE
Mated: trueEye color: red
Wing Beats/second: 220
Eye units: 760
Gender: FEMAIL
Number of eggs: 186Eye color: purple
Wing Beats/second: 220
Eye units: 760
Gender: FEMAIL
Number of eggs: 92
原型模式要依赖客户通过 不念克隆过程使用具体原型.在这个设计过程中, 客户是完成克隆的参与者, 由于克隆是原型设计中的关键要素, 所以客户是一个基本参与者, 而不仅仅是一个请求类.
现代企业组织
在创建型设计模式方面,现代企业组织就非常适合原型实现.现在企业组织往往是复杂而庞大的层次结构, 像面向对象编程一样,要为很多共同的特征建模.现在通过一个例子描述软件工程公司.
软件工程公司是一个典型的现代组织.工程部(Engineering Department)负责创建产品,管理部(Management)处理资源的协调组织,市场部(Marketing)负责产品的销售,推广和整体营销.
接口中的封装
在这个原型实现中,首先为程序的接口(一个抽象类)增加OOP,与所有原型接口一样,这个接口包含一个克隆操作.另外它还包含一些抽象和具体的获取方法和设置方法.其中有一个抽象获取方法/设置方法对,但要由3个具体原型实现为这个抽象获取方法/设置方法对提供具体实现.其他获取方法和设置方法分分别应用于员工名,ID码和照片等属性.注意所有这些属性都是保护属性(protected),所以尽管具体的获取方法和设置方法有公共可见性,但由于操作中使用的属性具有保护和可见性,这就提供了某种程度的封装:
<?php
abstract class IAcmePrototype
{
protected $name;
protected $id;
protected $employeePic;
protected $department;
//部门
abstract function setDept($orgCode);
abstract function getDept();
//名字
public function setName($emName)
{
$this->name = $emName;
}
public function getName()
{
return $this->name;
}
//ID
public function setId($emId)
{
$this->id = $emId;
}
public function getId()
{
return $this->id;
}
//雇员图像
public function setPic($ePic)
{
$this->employeePic = $ePic;
}
public function getPic()
{
return $this->employeePic;
}
abstract function __clone();
}
利用这些获取方法和设置方法, 所有属性的值都通过继承的保护变量来设置.采用这种设计, 扩展类及其实例可以得到更好的封装.
接口实现
3个IAcmePrototype子类都必须实现"dept"抽象方法以及__clone()方法.类似地, 每个具体原型类还包含一个常量UNIT,它提供一个赋值,可以由实例(包括克隆的对象)作为标识
首先来看市场部的Marketing类:
Marketing.php
<?php
include_once('IAcmePrototype.php');
class Marketing extends IAcmePrototype
{
const UNIT = "Marketing";
private $sales = "sales";
private $promotion = "promotion";
private $strategic = "strategic planning";
public function setDept($orgCode)
{
switch($orgCode)
{
case 101:
$this->department = $this->sales;
break;
case 102:
$this->department = $this->promotion;
break;
case 103:
$this->department = $this->strategic;
break;
default :
$this->department = "未识别的市场部";
}
}
public function getDept()
{
return $this->department;
}
public function __clone() {}
}
setDept()方法的实现使用了一个参数.并不是直接输入市场部的部门,这个方法的参数是一个数字码, 通过一个switch语句,限制了3种可接受的情况和默认情况,别外两个原型实现也类似
Management.php
<?php
include_once('IAcmePrototype.php');
class Management extends IAcmePrototype
{
const UNIT = "Management";
private $research = "research";
private $plan = "planning";
private $operations = "operations";
public function setDept($orgCode)
{
switch($orgCode)
{
case 201:
$this->department = $this->research;
break;
case 202:
$this->department = $this->plan;
break;
case 203:
$this->department = $this->operations;
break;
default :
$this->department = "未识别的管理部";
}
}
public function getDept()
{
return $this->department;
}
public function __clone() {}
}
Engineering.php
<?php
include_once('IAcmePrototype.php');
class Engineering extends IAcmePrototype
{
const UNIT = "Engineering";
private $development = "development";
private $design = "design";
private $sysAd = "system administration";
public function setDept($orgCode)
{
switch($orgCode)
{
case 301:
$this->department = $this->development;
break;
case 302:
$this->department = $this->design;
break;
case 303:
$this->department = $this->sysAd;
break;
default :
$this->department = "未识别的工程部";
}
}
public function getDept()
{
return $this->department;
}
public function __clone() {}
}
以上这3个具体原型实现分别有其特定用途,不过它们都符合接口,可以创建各个原型实现的一个实例, 然后根据需要克隆多个实例.这个克隆的工作由Client类完成
客户
客户的设置非常简单: 分别创建各个具体原型的一个实例, 然后按以下列表来克隆各个实例:
市场部门实例:
-----市场部门克隆
-----市场部门克隆
管理部门实例
-----管理部门克隆
工程部门实例
-----工程部门克隆
-----工程部门克隆
将来只使用这些克隆对象.使用获取方法和设置方法将各个特定情况的信息赋给这些克隆对象.以下是Client的实现
Client.php
<?php
function __autoload($class_name)
{
include_once $class_name.'.php';
}
class Client
{
private $market;
private $manage;
private $engineer;
public function __construct()
{
$this->makeConProto();
$Tess = clone $this->market;
$this->setEmployee($Tess, "Tess Smith" , 101, 'ts101-1234', 'tess.png');
$this->showEmployee($Tess);
$Jacob = clone $this->market;
$this->setEmployee($Jacob, "Jacob Jones" , 101, 'jj101-2234', 'jacob.png');
$this->showEmployee($Jacob);
$Ricky = clone $this->manage;
$this->setEmployee($Ricky, "Ricky Rodriguez" , 203, 'rr101-5634', 'ricky.png');
$this->showEmployee($Ricky);
$Olivaia = clone $this->engineer;
$this->setEmployee($Olivaia, "Olivaia Perez" , 302, 'op301-1278', 'olivia.png');
$this->showEmployee($Olivaia);
$John = clone $this->engineer;
$this->setEmployee($John, "John Jacson" , 101, 'jj301-14548', 'john.png');
$this->showEmployee($John);
}
private function makeConProto()
{
$this->market = new Marketing();
$this->manage = new Management();
$this->engineer = new Engineering();
}
private function showEmployee(IAcmePrototype $employeeNow)
{
$px = $employeeNow->getPic();
echo "<img src=$px width='150' height='150' /><br />";
echo $employeeNow->getName().'<br />';
echo $employeeNow->getDept().':'.$employeeNow::UNIT.'<br />';
echo $employeeNow->getId().'<hr />';
}
private function setEmployee(IAcmePrototype $employeeNow, $nm, $dp, $id, $px)
{
$employeeNow->setName($nm);
$employeeNow->setDept($dp);
$employeeNow->setId($id);
$employeeNow->setPic($px);
}
}
$worker = new Client();
解释:
客户Client的构造函数类包含3个私有属性, 用来分别实例化3个具体原型类. makeConPro()方法生成必要的实例.
接下来,使用克隆技术创建一个"员工"实例.然后,这个实例向一个设置方法setEmployee()发送特定的实例信息,这个设置方法使用IAcmePrototype接口类型提示,不过需要说明, 它只对第一个参数使用类型提示,其他参数都没有类型提示, 并不要求它们派生自IAcmePrototype接口.克隆"员工"可以使用IAcmePrototype抽象类的所有设置方法以及具体原型类实现的setDept()方法.
要使用各个员工的数据,Client类可以使用继承的获取方法.以下是运行Client输出的结果
Tess Smith
sales:Marketing
ts101-1234
Jacob Jones
sales:Marketing
jj101-2234
Ricky Rodriguez
operations:Management
rr101-5634
Olivaia Perez
design:Engineering
op301-1278
John Jacson
未识别的工程部:Engineering
jj301-14548
可以根据需要增加更多的克隆, 而且只需要对具体原型类完成一次实例化.使用原型模式时, 并不是建立具体类的多个实例,而只需要一个类实例化和多个克隆.
完成修改,增加特性
要记住,最重要(可能也是最基本)的是, 设计模式允许开发人员修改和增补程序,而不必一切从头开始.例如, 假设总裁决定公司增加一个新的部门,比如研究部门(Research), 这会很难吗?一点也不难.Research可以扩展IAcmePrototype抽象类, 然后实现抽象获取方法和设置方法来反映这个研究部门的组织.需要说明,Client类中获取方法和设置方法使用的代码提示指示一个接口,而不是一个抽象类的具体实现.所以, 只要增加的单元正确地实现了这个接口,就能顺利地增加到应用中, 而不会带来波动,也不需要对程序中的其他参与者进行重构.
不仅可以增加更多的具体类, 还可以很容易地对各个类进行修改, 而不会造成破坏.例如假设这个组织的市场部决定,除了现有的部门外, 他们还需要一个特殊的在线市场部,. 这样一来, switch/case操作需要一个新的分支(case), 还要有一个新的私有属性(变量)来描述新增的这个部门.这个改变将封冻在单独的类中, 而不会影响其他参与者.由于这种改变不会带来破坏, 所以应用的规模越大, 这一点就越重要.可以看到原型设计模式不仅支持一致性, 还支持灵活的改变.
PHP世界中的原型
由于PHP是一个服务器端语言,也是与MySQL数据库交互的一个重要工具,所以原型设计模式尤其适用 .并不是为数据库的第一个元素分别创建对象,PHP可以使用原型模式创建具体类的一个实例,然后利用数据库中的数据克隆其余的实例(记录).
了解克隆过程之后,与直接实例化的过程相比,你可能会问:"这有什么区别?" 换句话说,为什么克隆比直接实例化对象需要的资源少?它们的区别并不能直接看到. 一个对象通过克隆创建实例时, 它不会启动构造函数.克隆能得到原始类的所有属性, 甚至还包含父接口的属性,另外还继承了传递实例化对象的所有值.构造函数生成的所有值以及存储在对象属性中的值都会成为克隆对象的一部分.所以没有返回构造函数.如果发现你的克隆对象确实需要访问构造函数生成的值但又无法访问, 这说明需要对类进行重构,使实例能拥有它们需要的一切信息, 而且可以把这些数据传递给克隆对象.
总的来说, 原型模式在很多不同类型的PHP项目中都很适用, 如果解决一个问题需要乃至创建型模式, 都可以使用原型模式.