一、概述
Prototype(原型)模式用于动态抽取当前对象运行时的状态,从自身构造出一个新的对象,即自身的拷贝(往往是深拷贝),如果你愿意,你可以叫它Clone模式。
二、结构
Prototype模式的结构如下图所示:
图1:Prototype模式
三、应用
Prototype模式在需要拷贝的产品的类型需动态指定时经常被用到。对于类似绘图软件这样的以对象管理为主要目的的应用系统中,各元素往往需要支持动态拷贝动作,因此常常会运用Prototype模式。
与Builder模式等不同,Prototype的产品构建工作是由原型产品完成的,产品通过拷贝对象自身(深拷贝)来完成新产品的构建(从而无需创建一个与产品类层次平行的工厂类层次,扩展产品类型也不会影响到产品创建代码,由于存在一个间接层Director,Builder模式在扩展产品类型时一般也不会影响到客户代码,甚至是Director类的代码,Factory模式由于需要创建与产品类层次平行的工厂类层次,当产品类型发生变化时,产品创建代码不可避免会受到影响),而其它几个Creational Patterns是通过组装或直接创建其它产品来达到向上级应用提交新产品的目的。
在应用Prototype模式在有些情况下可以简单地被拷贝构造函数所替代,但Prototype模式的应用使得拷贝产品变得更加灵活而强大,由于跟构造函数一样,拷贝构造函数也不能是虚函数,这使得我们在很多情况下,尤其是ConcretePrototype内部组成不同的情况下无法使用拷贝构造函数来创建产品(除非根据待拷贝产品的type,使用type-switch来分别调用不同的构造函数),而Prototype模式可以使我们在无需考虑ConcretePrototype类型的情况下对对象进行拷贝,但在ConcretePrototype内部,有些情况下可以考虑将clone操作转发给拷贝构造函数来完成,如:
ConcretePrototype* ConcretePrototype::clone() const
{
return new ConcretePrototype( *this );
}
但是需要注意的是,在构造函数中安排过多的操作也不利于程序的维护,clone方法作为一个普通成员函数,在使用和维护上更方便(以抛出异常为例,如果你的构造函数抛出异常,将使得客户代码编写时麻烦重重,虽然可以这么做。此外,由于new会自动检查内存耗尽情况,当内存耗尽时,构造函数会抛出std::bad_alloc异常,但我们往往不会去处理该异常,与其让我们的程序被这个难得一见的异常弄得遍体鳞伤,还不如干脆在程序出现该异常时直接跟用户说Bye-bye。同样,一个异常已经够让人烦了,谁会愿意再加个自定义异常进去?)。
GDI+(Windows平台图形支持库)的Image类就提供了Clone方法,以便我们进行Image子类对象的动态拷贝,对于我们的应用开发,当需要支持产品的动态拷贝时,应认真考虑是否需要提供clone方法,以免客户代码必须通过type-switch来根据产品的确切类型创建新产品,加大类与类之间的耦合度。
虽然有以上原因使得我们在很多情况下需要考虑使用Prototype模式,为我们的类提供Clone方法,但从目前看到的很多情况来看,提供Clone方法只是为了提供一个更加明确的语义,对于很多人来讲,Clone方法比拷贝构造函数更容易理解。个人观点,GDI+的大多数类提供拷贝构造函数可能是为了方便使用其它编程语言的用户,同时,也为了以后扩展的方便。
四、优缺点
优点:
1、通过复制自身来创建新产品,客户不知道需要对象的实际类型,只需知道它的抽象基类即可;
2、Prototype模式的应用使得扩展产品类型变得变得比较容易,不会影响到其它产品,甚至是客户代码;
3、当我们需要拷贝一个包含多种元素(同一基类)的容器时,我们需要做的工作将变得十分简单。
缺点:必须先有一个对象实例(即原型)才能clone(所以,clone模式并不能成为其它创建型模式的更好的替代品)。
五、扩展
Prototype模式可以进一步扩展成带PrototypeManager的Prototype模式,在这种情况下,我们先使用PrototypeManager来创建具体原型类的对象,并记录每一个被创建的对象,以后需要拷贝原型对象时也通过PrototypeManager来完成,这从一定程度上解决了在clone前必须已经有一个对象实例的问题。
六、举例
Prototype模式并不是简简单单一个clone方法,Prototype模式的意义在于动态抽取当前对象运行时的状态,同时通过提供统一的clone接口方法,使得客户代码可以在不知道对象具体类型时仍然可以实现对象的拷贝,而无需运用type-switch检测对象的类型信息来分别调用创建方法来创建一个新的拷贝。
Prototype模式常常被用于与Flyweight模式(利用Prototype模式构造享元对象的拷贝)、State模式(利用Prototype模式构造状态类对象的拷贝,以供在对象间传递这些状态信息)、Strategy模式(与State模式下类似,利用Prototype模式构造Strategy类对象的拷贝,在不同的StrategyUser类间传递Strategy对象)等结合使用。
以某使用GDI+进行图片处理的程序为例,其中多个功能需要预览图片即将进行的处理(如Sharp、Darken、Rotate等)所能实现的效果,在用户没有最终确定该处理之前,需要复制一个当前Image对象的拷贝,然后对该拷贝进行处理,在创建该拷贝时存在一个问题,由于我们使用的是基类Image的指针,因此存在前面所说的问题,不能直接调用拷贝构造函数来创建新对象,同时,也不可能跟创建原型对象一样从文件流建立对象,而且,对象当前所处的状态是经过处理的,与创建之初的状态并不一样。为了便于我们解决类似这样的问题,Image类提供了一个Clone方法,以便我们进行对象的拷贝,Clone方法是虚基类Image的虚方法,由各子类具体实现。
下面的示例简单模拟了Image类的实现策略:
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Image
{
virtual Image* clone() = 0;
virtual void reform() { cout << "reforming Image[" << this << "]" << endl; }
};
class Bitmap : public Image
{
public:
Bitmap(const string& filename) : filename(filename) {}
Bitmap(const Bitmap& pic) : filename(pic.filename) {}
Image* clone() { return new Bitmap(*this); } // to simplify code, just call copy-ctor to create a clone
private:
string filename;
// other attributes
};
class OtherPicFormat : public Image
{
public:
OtherPicFormat(const string& filename, int otherinitialoption) :
filename(filename), otherinitialoption(otherinitialoption) {}
OtherPicFormat(const OtherPicFormat& pic) :
filename(pic.filename), otherinitialoption(pic.otherinitialoption) {}
Image* clone() { return new OtherPicFormat(*this); } // to simplify code, just call copy-ctor to create a clone
private:
string filename;
int otherinitialoption;
// other attributes
};
int main()
{
Image* ba[] = {new Bitmap("abc"), new OtherPicFormat("cde", 0)};
vector<Image*> vb;
copy(ba, ba + sizeof(ba) / sizeof(Image*), back_inserter(vb));
Image* pb = NULL;
for (vector<Image*>::iterator it = vb.begin(); it != vb.end(); ++it)
{
pb = (*it)->clone(); // clone will create a new object
pb->reform();
delete pb;
delete *it;
}
return 0;
}
参考:
1、http://blog.csdn.net/blackphoenix/archive/2002/08/19/14151.aspx