这次我们来讲一下原型模式。总体来讲原型模式还是比较简单的。
意图
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
结构图
这个结构图还是挺简单的,Prototype就是产品类。
和前面的抽象工厂,工厂方法等模式相比,产品类就增加了一个函数Clone()。
我们还是以之前的地形例子来讲解这个模式,先给出类图
我这次把CFactory当作原型模式的client。
先看看产品类的实现:
class CComponent
{
public:
virtual void LoadPicture() = 0;
virtual CComponent* Clone() = 0;
};
class CSnowBackground: public CComponent
{
public:
virtual void LoadPicture()
{
std::cout<< "Load snow background picture \n";
}
virtual CComponent* Clone()
{
CComponent* clone = new CSnowBackground();
return clone;
}
};
class CSnowGround: public CComponent
{
public:
virtual void LoadPicture()
{
std::cout<< "Load snow ground picture\n";
}
virtual CComponent* Clone()
{
CComponent* clone = new CSnowGround();
return clone;
}
};
跟之前抽象工厂模式对比,这次我们只是增加了一个函数Clone(),然后实现它。
这个例子的Clone里面,我只是创造了一个新的空对象,假如当前类有一些属性的话,那么我们可以在Clone里面把当前对象的属性复制给新创建的对象。
看看client类CFactory的实现:
class CFactory
{
public:
//生产一个Terrain对象
virtual CTerrain* MakeTerrain()
{
return new CTerrain();
}
virtual CComponent* MakeBackground() = 0;
virtual CComponent* MakeGround() = 0;
//生产一个天气对象
virtual CWeather* MakeWeather() = 0;
};
class CProtoFactory: public CFactory
{
public:
CProtoFactory(CComponent* bg, CComponent* gr)
{
m_bg = bg;
m_gr = gr;
}
virtual CComponent* MakeBackground()
{
return m_bg->Clone();
}
virtual CComponent* MakeGround()
{
return m_gr->Clone();
}
private:
CComponent* m_bg;
CComponent* m_gr;
};
CFactory还是没有变。然后我把CFactory的子类改变了一下:
1. 去掉了singleton;
2. 增加了2个数据成员,背景实例和地面实例;
3. 然后在构造函数里面增加了2个参数。
4. 在MakeBackground和MakeGround里面不再调用产品类的构造函数,取而代之的是调用产品类的Clone函数。
再看看CCreator类的实现,跟抽象工厂模式几乎没有分别,(这里为了简单化,我去掉了天气对象的创建)
class CCreator
{
public:
void Create(CFactory& factory, CTerrain** t)
{
CTerrain* terrain = factory.MakeTerrain();
CComponent* bg = factory.MakeBackground();
CComponent* ground = factory.MakeGround();
bg->LoadPicture();
ground->LoadPicture();
terrain->SetBackground(bg);
terrain->SetGround(ground);
*t = terrain;
}
};
那么客户端怎么调用呢,看下面代码:
CComponent* bg = new CSnowBackground();
CComponent* gr = new CSnowGround();
CProtoFactory factory(bg, gr);
CCreator creator;
CTerrain* terrain = NULL;
creator.Create(factory, &terrain);//这里就得到了以前面的2个原型创建出来的地形实例
关键在于前面2行定义的2个原型,这2个原型被传递到了工厂实例里面,从而使得工厂可以生产以这2个实例为原型的产品。
假如我们拿原型模式和抽象工厂模式相比,我们会发现,对于抽象工厂模式,每增加一些新产品系列,就得相应的增加子工厂类。
而原型模式,就不需要增加新的子工厂类,只要指定原型就好了,比如我们现在要创建森林地形。那么可以这么做。
CComponent* bg = new CForestBackground();
CComponent* gr = new CForestGround();
CProtoFactory factory(bg, gr);
CCreator creator;
CTerrain* terrain = NULL;
creator.Create(factory, &terrain);//这里就得到了森林地形实例
跟前面的代码相比较,唯一的区别就是原型不同,其他一样。
好了,原型模式就基本介绍完了。
其实总体来讲原型模式还是蛮简单的,我觉得里面的关键应该就是Clone函数怎么实现了。
如果一个类很简单,那么Clone函数也很容易实现,但是如果一个类的数据成员比较复杂,里面有成员不支持拷贝或者有循环引用等等,这个时候实现Clone函数就比较困难了。
做过C#和JAVA的朋友会发现,其实,C#和JAVA里面本身就可以支持原型模式了。
C#和JAVA 都有反射机制。也就是说可以在运行的时候从一个变量上面获取变量类型的信息,我们称之为类对象,这个类对象里面包含了很多类型信息,比如数据成员,成员函数等等。而且可以从类对象来创建新的实例。那么对于C#和JAVA来说基本就不需要原型模式了。因为类对象就是原型。原型模式对于C++来说还是有一定作用的。