意图:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新对象。
适用:
系统应该独立于产品创建、构成和表示时,可分为以下三种情形:
1、要实例化的类需要运行时刻加载时;
2、需要避免创建与产品类层次结构平行的工厂层次;【解决工厂方法可能存在的问题】
3、当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们
可能比每次用合适的状态手工实例化该类更方便一些。【怎么理解?就是说一个对象只有几种不同的实例,我们可以预先初始化好后所有的实例,并在使用时拷贝他们,这可能会比每次创建要简单。】
参与者:
Prototype
ConcretePrototype
Client
效果:
1、同抽象工厂、生成器模式一样,对客户隐藏了具体的产品类【由于工厂方法是将对象的创建延迟化,它意味着所创建对象的细节是由客户端决定,这也正是抽象工厂与工厂方法的区别,参见《工厂方法》中我关于抽象工厂与工厂方法的比较。从这段《设计模式》原著上的描述,我想关于抽象工厂与工厂方法区别的理解应该是对的。】
2、运行时刻增加和删除产品【假设我们写个卡牌类的游戏,采用原型模式后,以后可以方便添加更多功能的卡牌到游戏中,而不必要修改相关代码】;
3、改变值以指定新对象【还是回到卡牌类游戏上,假设双人玩法与多人玩法有不一样的行为,我们可以将职责委托给原型类,从而达到类似于增加新“类”的效果】
4、改变结构以指定新对象【这里的结构是指一种与集合有关的结构,如某种树形的层次结构】
5、减少子类的构造【由于是通过对象本身克隆新实例,因此相较工厂方法而言不需要Creator对象,从而减少相关类的子类,这对于C++/C#语言很有用,而对于类类型语言作用不明显】
6、用类动态配置应用【由于应用中是利用原型的克隆方法复制新类进行使用,而原型实例可通过Client初始化,因此达到动态配置应用的目的】
缺点:
每个原型都必须实现一个Clone操作;
实现:
1、在Self语言中天生拥有,在Smalltalk及Objective C中亦不重要,C++、C#中还是很有用的;
2、当系统中原型数目不固定时,可实现一个原型管理器用来注册原型实例;
3、实现克隆操作:尤其存在循环引用时,更具有难度。克隆可分为浅表克隆与深表克隆,序列化是克隆的技术手段之一。
4、一般来说不应在Clone方法中传递参数,而应该提供Initliaze方法进行对象拷。原型本身的创建经常采用工厂方法模式。
相关:AbstractFactory[竞争]、FactoryMethod[协作、竞争]、Composite与Decorator[经常使用到原型模式(效果3、4)]
延伸:
Microsoft:关于IClone接品。首先我不认为他实现了原型模式。原型模式强调用原型实例指定对象的创建,其目的是解决三个适用性问题,而不是机械的将new操作替换成Clone方法。但微软的DataTable中Clone方法常是原型模式的应用场合。假如一个系统我们拥有同一Schema的DataTable大量实例,而程序运行前不知道DatabTable的具体Schema,最佳方案就是DataTable是采用原型模式由运行时构建相应的Schema.【题外:关于《大话设计模式》中关于原型模式的取例是有问题的,它只是机械的将new改成Clone。虽然在C++等语言中可通过memcpy实现深表拷贝提高效率,但从意图上来说不是原型模式。IClone光有复制效果,没有原型思想。】
示例:
C#
下面是一个基于模板的报表类的设计。随着应用的变化,会出现新的报表模板。而控制报表生成的程序本身并不需要变化。因此,在设计上,我们可采用原型模式,在程序初始化阶段将各种类型的模板实例注册到原型管理器【在下例中,原型管理器为该类本身】
namespace Sysnet.Environments.Business.Reports
{
public class TemplateReport : ITemplateReport
{
protected static IDictionary<string , ITemplateReport > templates = new Dictionary<string,ITemplateReport>();
public static void Register(ITemplateReport template)
{
if (templates.ContainsKey(template.Template.ToString()) == false)
templates[template.Template.ToString()] = template;
}
public static ITemplateReport Query(Uri templateUri)
{
return templates[templateUri.ToString()];
}
public static void Remove(Uri templateUri)
{
templates.Remove(templateUri.ToString());
}
public static IEnumerable<ITemplateReport> Templates
{
get
{
foreach (var c in templates)
yield return c.Value;
}
}
public virtual ITemplateReport Clone()
{
TemplateReport report = new TemplateReport() { Template = this.Template };
return report;
}
public Uri Template
{
get;
set;
}
public virtual Uri Make(DataSet dataset)
{
return null;
}
}
}
C++
考虑设计一个通讯包处理器,服务器需要同时兼容不同版本的客户端通讯程序,我们可以利用原型模式,创建不同版本的数据包原型,并注册到通讯包处理器中。下面是样关代码
#pragma once
#include "commuprt.h"
#include <map>
#include <vector>
using namespace std;
class COMMUPRT_API CCommunicationBusinessPackage :public CPackage
{
public:
virtual CCommunicationBusinessPackage * Clone();
public:
enum
{
enumVersion , //版本号字段偏移
enumFlag = enumVersion + enumVersionLen,//标志位字段偏移
enumCmdOrResult = enumFlag + enumFlagLen,//命令执行结果、数据类型、命令类型字段偏移
enumPackageNo = enumCmdOrResult +enumCmdOrResultLen,//发送客户端编号偏移
enumRequestPackageNo = enumPackageNo + enumPackageNoLen,//应答包的序列号偏移
enumContentOffset = enumRequestPackageNo + enumPackageNoLen,//内容区偏移
enumHeaderLen = enumContentOffset//头部总长
};
protected:
union
{
char m_buffer[enumHeaderLen];//包头存储区
struct
{
char Version; //包的版本号
char flag; //标志位
};
short CmdOrDataTypeCode; //命令或数据类型代码
int PackageNo; //包序列号
int RequestPackageNo; //应答包的序列号
};
} m_packageHeader;
int BuildSerialNo(); //生成包串号
};
CCommunicationBusinessPackage * CCommunicationBusinessPackage::Clone()
{
CCommunicationBusinessPackage * packages = new CCommunicationBusinessPackage();
memcpy(&packages->m_packageHeader,&this->m_packageHeader,sizeof(m_packageHeader));
BuildSerialNo();
return packages;
}
class COMMUPRT_API CBusinessPackageProcessor : public CBasePackageProcessor, private CRemoteCommandCreator
{
public:
void RegisterPackagePrototype(int version,int packageType,CCommunicationBusinessPackage*);//用于注册原型的方法
protected:
int MakePrototypeKey(int version,int packageType);
map<int,CCommunicationBusinessPackage*> map_PackagePrototypes;
};
工厂方法与原型模式在许多地方是竞争关系,但工厂方法对以同一个类型不同状态实例创建对象显得有点力不从心,而原型模式也不太适合解决对象延迟化创建的问题。