概述

我们都知道,创建型模式一般是用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作。我们通过原型模式可以快速的创建一个对象,而不需要提供专门的构造函数,特别对于对象构造非常复杂的类来说,这无疑是一种非常有效的方式。

例子:
下面是一个邮寄快递的场景:
“给我寄个快递。”顾客说。
“寄往什么地方?”你问。
“和上次差不多一样,只是邮寄给另外一个地址,这里是邮寄地址……”顾客一边说一边把写有邮寄地址的纸条给你。
“好!”你愉快地答应,因为你保存了用户的以前邮寄信息,只要复制这些数据,然后通过简单的修改就可以快速地创建新的快递数据了。

问题

当对象的构造函数非常复杂,生成新对象非常耗时间、耗资源时,怎么来快速创建新对象呢?

解决方案

原型模式:通过复制(克隆、拷贝)一个指定类型的对象来创建更多同类型的对象。这个指定的对象可被称为“原型”对象。

适用性

原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般调用原型对象的内部提供克隆的方法,通过该方法返回一个对象的副本。这种创建对象的方式,相比我们之前说的几类创建型模式还是有区别的,之前的讲述的工厂模式与抽象工厂都是通过工厂封装具体的new操作的过程,返回一个新的对象,有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景的时候,可能使用原型模式更简单也效率更高。

  1. 当一个系统应该独立于它的产品创建、构成和表示时,要使用 Prototype模式
  2. 当要实例化的类是在运行时刻指定时,例如,通过动态装载;
  3. 为了避免创建一个与产品类层次平行的工厂类层次时
  4. 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。(也就是当我们在处理一些对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,可能我们使用原型模式更合适)。

结构

原型模式结构如图所示:
prototype

组成

  1. 客户(Client)角色:使用原型对象的客户程序
  2. 抽象原型(Prototype)角色:规定了具体原型对象必须实现的接口(如果要提供深拷贝,则必须具有实现clone的规定)
  3. 具体原型(ConcretePrototype):从抽象原型派生而来,是客户程序使用的对象,即被复制的对象。此角色需要实现抽象原型角色所要求的接口。

效果

Prototype模式有许多和Abstract Factory模式 和 Builder模式一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字的数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。
下面列出Prototype模式的另外一些优点。

  1. 运行时刻增加和删除产品: Prototype允许只通过客户注册原型实例就可以将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时刻建立和删除原型。
  2. 改变值以指定新对象: 高度动态的系统允许你通过对象复合定义新的行为—例如,通过为一个对象变量指定值—并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。客户可以将职责代理给原型,从而表现出新的行为。这种设计使得用户无需编程即可定义新“类” 。实际上,克隆一个原型类似于实例化一个类。Prototype模式可以极大的减少系统所需要的类的数目。
  3. 改变结构以指定新对象:许多应用由部件和子部件来创建对象。
  4. 减少子类的构造 Factory Method 经常产生一个与产品类层次平行的 Creator类层次。Prototype模式使得你克隆一个原型而不是请求一个工厂方法去产生一个新的对象。因此你根本不需要Creator类层次。这一优点主要适用于像 C + +这样不将类作为一级类对象的语言。像Smalltalk和Objective C这样的语言从中获益较少,因为你总是可以用一个类对象作为生成者。在这些语言中,类对象已经起到原型一样的作用了。
  5. 用类动态配置应用 一些运行时刻环境允许你动态将类装载到应用中。在像 C + +这样的语言中,Prototype模式是利用这种功能的关键。一个希望创建动态载入类的实例的应用不能静态引用类的构造器。而应该由运行环境在载入时自动创建每个类的实例,并用原型管理器来注册这个实例(参见实现一节) 。这样应用就可以向原型管理器请求新装载的类的实例,这些类原本并没有和程序相连接。 ET++应用框架[WGM88]有一个运行系统就是使用这一方案的。

Prototype的主要缺陷是每一个Prototype的子类都必须实现clone操作,这可能很困难。
例如,当所考虑的类已经存在时就难以新增 clone操作。当内部包括一些不支持拷贝或有循环引用的对象时,实现克隆可能也会很困难的。

实现

cpp

带Prototype Manager的原型模式

原型模式的第二种形式是带原型管理器的原型模式,其UML图如下:
prototype_manager

原型管理器(Prototype Manager)角色:创建具体原型类的对象,并记录每一个被创建的对象

下面这个例子演示了在原型管理器中存储用户预先定义的颜色原型,客户通过原型管理器克隆颜色对象。

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 ,'
'; } } /** * 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(); ?>