设计模式(四)原型模式Prototype
1. 概述
我们都知道,创建型模式一般是用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象而不需要提供专门的new()操作就可以快速完成对象的创建,这无疑是一种非常有效的方式,快速的创建一个新的对象。
例子1:孙悟空拔下一嘬猴毛,轻轻一吹就会变出好多的孙悟空来。
例子2:寄个快递
下面是一个邮寄快递的场景:
“给我寄个快递。”顾客说。
“寄往什么地方?寄给……?”你问。
“和上次差不多一样,只是邮寄给另外一个地址,这里是邮寄地址……”顾客一边说一边把写有邮寄地址的纸条给你。
“好!”你愉快地答应,因为你保存了用户的以前邮寄信息,只要复制这些数据,然后通过简单的修改就可以快速地创建新的快递数据了。
2. 问题
当对象的构造函数非常复杂,在生成新对象的时候非常耗时间、耗资源的情况?我们是怎么来创建呢?
3. 解决方案
原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是有对象的内部提供克隆的方法,通过该方法返回一个对象的副本,这种创建对象的方式,相比我们之前说的几类创建型模式还是有区别的,之前的讲述的工厂模式与抽象工厂都是通过工厂封装具体的new操作的过程,返回一个新的对象,有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景的时候,可能使用原型模式更简单也效率更高。
• 1)当一个系统应该独立于它的产品创建、构成和表示时,要使用 Prototype模式
• 2)当要实例化的类是在运行时刻指定时,例如,通过动态装载;
• 3)为了避免创建一个与产品类层次平行的工厂类层次时
• 4)当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。(也就是当我们在处理一些对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,可能我们使用原型模式更合适)。
5. 结构
客户(Client)角色:使用原型对象的客户程序
抽象原型(Prototype)角色:规定了具体原型对象必须实现的接口(如果要提供深拷贝,则必须具有实现clone的规定)
具体原型(ConcretePrototype):从抽象原型派生而来,是客户程序使用的对象,即被复制的对象。此角色需要实现抽象原型角色所要求的接口。
7. 效果
Prototype模式有许多和Abstract Factory模式 和 Builder模式一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
下面列出Prototype模式的另外一些优点。
1 ) 运行时刻增加和删除产品: Prototype允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。
2 ) 改变值以指定新对象: 高度动态的系统允许你通过对象复合定义新的行为—例如,通过为一个对象变量指定值—并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。这种设计使得用户无需编程即可定义新“类” 。实际上,克隆一个原型类似于实例化一个类。Prototype模式可以极大的减少系统所需要的类的数目。
3) 改变结构以指定新对象:许多应用由部件和子部件来创建对象。
4) 减少子类的构造 Factory Method 经常产生一个与产品类层次平行的 Creator类层次。Prototype模式使得你克隆一个原型而不是请求一个工厂方法去产生一个新的对象。因此你根本不需要Creator类层次。这一优点主要适用于像 C + +这样不将类作为一级类对象的语言。像Smalltalk和Objective C这样的语言从中获益较少,因为你总是可以用一个类对象作为生成者。在这些语言中,类对象已经起到原型一样的作用了。
5) 用类动态配置应用 一些运行时刻环境允许你动态将类装载到应用中。在像 C + +这样的语言中,Prototype模式是利用这种功能的关键。一个希望创建动态载入类的实例的应用不能静态引用类的构造器。而应该由运行环境在载入时自动创建每个类的实例,并用原型管理器来注册这个实例(参见实现一节) 。这样应用就可以向原型管理器请求新装载的类的实例,这些类原本并没有和程序相连接。 E T + +应用框架[ W G M 8 8 ]有一个运行系统就是使用这一方案的。
Prototype的主要缺陷是每一个Prototype的子类都必须实现clone操作,这可能很困难。例如,当所考虑的类已经存在时就难以新增 clone操作。当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难的。
8. 实现
- <?php
- /**
- * 原型模式
- */
-
- /**
- * 抽象原型角色
- */
- interface Prototype {
- public function copy();
- }
-
- /**
- * 具体原型角色
- */
- class ConcretePrototype implements Prototype{
-
- private $_name;
-
- public function __construct($name) {
- $this->_name = $name;
- }
-
- public function setName($name) {
- $this->_name = $name;
- }
-
- public function getName() {
- return $this->_name;
- }
-
- public function copy() {
- /** 深拷贝 */
- return clone $this;
- /** 浅拷贝 */
- //return $this;
- }
- }
-
-
- class Client {
-
- /**
- * Main program.
- */
- public static function main() {
- $object1 = new ConcretePrototype(11);
- $object_copy = $object1->copy();
-
- var_dump($object1->getName());
- echo '<br />';
- var_dump($object_copy->getName());
- echo '<br />';
-
- $object1->setName(22);
- var_dump($object1->getName());
- echo '<br />';
- var_dump($object_copy->getName());
- echo '<br />';
- }
- }
-
- Client::main();
- ?>
9. 浅拷贝和深拷贝
- <?php
- /**
- * 原型模式
- */
- /**
- * 抽象原型角色
- */
- interface Prototype {
- public function copy();
- }
- /**
- * 具体原型角色
- */
- class ConcretePrototype implements Prototype{
- private $_name;
- public function __construct($name) {
- $this->_name = $name;
- }
- public function setName($name) {
- $this->_name = $name;
- }
- public function getName() {
- return $this->_name;
- }
- public function copy() {
- /** 深拷贝 */
- return clone $this;
- /** 浅拷贝 */
- //return $this;
- }
- }
- class Client {
- /**
- * Main program.
- */
- public static function main() {
- $object1 = new ConcretePrototype(11);
- $object_copy = $object1->copy();
- var_dump($object1->getName());
- echo '<br />';
- var_dump($object_copy->getName());
- echo '<br />';
- $object1->setName(22);
- var_dump($object1->getName());
- echo '<br />';
- var_dump($object_copy->getName());
- echo '<br />';
- }
- }
- Client::main();
- ?>
原型模式的原理图:
浅拷贝
被拷贝对象的所有变量都含有与原对象相同的值,而且对其他对象的引用仍然是指向原来的对象。即浅拷贝只负责当前对象实例,对引用的对象不做拷贝。浅复制后的对象和对象副本的情况:
深拷贝
被拷贝对象的所有的变量都含有与原来对象相同的值,除了那些引用其他对象的变量。那些引用其他对象的变量将指向一个被拷贝的新对象,而不再是原有那些被引用对象。即 深拷贝把要拷贝的对象所引用的对象也都拷贝了一次,而这种对被引用到的对象拷贝叫做间接拷贝。深复制的对象和对象副本的情况:
深拷贝要深入到多少层,是一个不确定的问题。
在决定以深拷贝的方式拷贝一个对象的时候,必须决定对间接拷贝的对象是采取浅拷贝还是深拷贝还是继续采用深拷贝。
因此,在采取深拷贝时,需要决定多深才算深。此外,在深拷贝的过程中,很可能会出现循环引用的问题。
10. 带Prototype Manager的原型模式
原型模式的第二种形式是带原型管理器的原型模式,其UML图如下:
原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象。
下面这个例子演示了在原型管理器中存储用户预先定义的颜色原型,客户通过原型管理器克隆颜色对象。
- <?php
- /**
- * abstract Prototype
- *
- */
- abstract class ColorPrototype
- {
- //Methods
- abstract function copy();
- }
- /**
- * Concrete Prototype
- *
- */
- class Color extends ColorPrototype{
- //Fields
- private $red;
- private $green;
- private $blue;
- //Constructors
- function __construct( $red, $green, $red) {
- $this->red = $red;
- $this->green = $green;
- $this->blue = $red;
- }
- /**
- * set red
- *
- * @param unknown_type $red
- */
- public function setRed($red) {
- $this->red = $red;
- }
- /**
- * get red
- *
- */
- public function getRed(){
- return $this->red;
- }
- /**
- *set Green
- *
- * @param $green
- */
- public function setGreen($green) {
- $this->green = $green;
- }
- /**
- * get Green
- *
- * @return unknown
- */
- public function getGreen() {
- return $this->green ;
- }
- /**
- *set Blue
- *
- * @param $Blue
- */
- public function setBlue($Blue) {
- $this->blue = $Blue;
- }
- /**
- * get Blue
- *
- * @return unknown
- */
- public function getBlue() {
- return $this->blue ;
- }
- /**
- * Enter description here...
- *
- * @return unknown
- */
- function copy(){
- return clone $this;
- }
- function display() {
- echo $this->red , ',', $this->green, ',', $this->blue ,'<br>';
- }
- }
- /**
- * Enter description here...
- *
- */
- class ColorManager
- {
- // Fields
- static $colors = array();
- // Indexers
- public static function add($name, $value){
- self::$colors[$name] = $value;
- }
- public static function getCopy($name) {
- return self::$colors[$name]->copy();
- }
- }
- /**
- *Client
- *
- */
- class Client
- {
- public static function Main()
- {
- //原型:白色
- ColorManager::add("white", new Color( 255, 0, 0 ));
- //红色可以由原型白色对象得到,只是重新修改白色: r
- $red = ColorManager::getCopy('white');
- $red->setRed(255);
- $red->display();
- //绿色可以由原型白色对象得到,只是重新修改白色: g
- $green = ColorManager::getCopy('white');
- $green->setGreen(255);
- $green->display();
- //绿色可以由原型白色对象得到,只是重新修改白色: b
- $Blue = ColorManager::getCopy('white');
- $Blue->setBlue(255);
- $Blue->display();
- }
- }
- ini_set('display_errors', 'On');
- error_reporting(E_ALL & ~ E_DEPRECATED);
- Client::Main();
- ?>