原型模式 Prototype Pattern
?
一. 原型模式简介
原型模式(Prototype Pattern)也是一种创建型模式,它关注的是大量相似对象的创建问题。我们经常会遇到这样的情况:在系统中要创建大量的对象,这些对象之间具有几乎完全相同的功能,只是在细节上有一点儿差别。
这样的情形经常遇到。三国系列游戏是我最喜欢的游戏系列之一。你有没有注意到那里边上百位英雄的头像基本上很相似?你仔细区分就会发现,虽然每个人都不同,但基本上只具有几种脸型:长方的、圆形的、细长的,然后配上不同的胡子、眉毛、眼睛、嘴,有的再加点儿伤疤或装饰物(比如给独眼龙夏侯敦加个单眼罩),就成了不同的人物头像!那么,为什么会这样的?因为根据研究表明,人类的脸谱基本上只有有限的几个类型,只不过在细节和组合方面存在些许差异。游戏制作者具有依据这个理论,只对人脸进行有限的几种建模,然后再通过组合、修饰,就可以产生无数的头像了。
用面向对象的方法来说就是,我们先建立一个原型,然后通过对原型进行复制和修饰的方法,就可以产生一个和原型相似的新对象。用GoF的话来说就是:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
?
二. 原型模式实例 前几天,我很不幸把屋门的钥匙给弄丢了,结果进不了家门。万幸的是,GF那儿还有一把,于是第二天我拿了她的那把去配钥匙。另外,她还让我顺便给她配一把橱柜的钥匙。现在配个钥匙真是简单,把钥匙给他,他直接找一个合适的钥匙胚子,把我的钥匙夹在配钥匙机的一端,胚子夹在另一端,一开电源,一把标尺比着我的钥匙齿型走一遍,砂轮就在胚子上复制出一把钥匙来!一分钟不到,两把新钥匙就搞定了!?
????? 用OO来描述,我的 旧钥匙是一个原型。配钥匙的过程就是根据我提供的原型,再复制一份出来,就有了一个新的钥匙。两个钥匙完全一样,我也可以给新配的钥匙贴个标签,以表明是“我的”。用这样的方法,我可以对各种钥匙进行复制,也可以复制无限多份。OO分析的类图如下:?
????? 程序代码如下:
namespace?Prototype
{
????//抽象钥匙原型
????public?abstract?class?Key
????{
????????private?string?name;
?
????????public?string?Name
????????{
????????????get?{?return?name;?}
????????????set?{?name?=?value;?}
????????}
????????private?string?owner;
?
????????public?string?Owner
????????{
????????????get?{?return?owner;?}
????????????set?{?owner?=?value;?}
????????}
????????public?Key(string?name,?string?owner)
????????{
????????????this.name?=?name;
????????????this.owner?=?owner;
????????}
????????//钥匙复制自身的抽象定义
????????public?abstract?Key?Clone();
????????public?override?String?ToString()
????????{
????????????return?this.Name?+?",?belongs?to?"?+?this.Owner;
????????}
????}
????//大门钥匙
????public?class?GateKey?:?Key
????{
????????public?GateKey(string?owner)?:?base("Gate?Key",?owner)?{?}
?
????????public?override?Key?Clone()
????????{
????????????return?new?GateKey(this.Owner);
????????}
????}
????//橱柜钥匙
????public?class?CabinetKey?:?Key
????{
????????public?CabinetKey(string?owner)?:?base("Cabinet?Key",?owner)?{?}
?
????????public?override?Key?Clone()
????????{
????????????return?new?CabinetKey(this.Owner);
????????}
????}
????//客户调用方法
????public?class?Client
????{
????????public?static?void?Main(string[]?args)
????????{
????????????Key?oldGateKey,?newGateKey,?oldCabinetKey,?newCabinetKey;
????????????oldGateKey?=?new?GateKey("GF");
????????????newGateKey?=?oldGateKey.Clone();
????????????newGateKey.Owner?=?"Me";
????????????oldCabinetKey?=?new?CabinetKey("Me");
????????????newCabinetKey?=?oldCabinetKey.Clone();
????????????newCabinetKey.Owner?=?"GF";
?
????????????Console.WriteLine(oldGateKey);
????????????Console.WriteLine(newGateKey);
????????????Console.WriteLine(oldCabinetKey);
????????????Console.WriteLine(newCabinetKey);
????????}
????}
} 这就是原型模式的典型实现:把一个现有对象作为原型,通过对原型进行复制产生新的对象。这样做的好处是:可以通过对原型对象的多次复制,可以产生无限的新对象。1. Clone()方法
原型模式中复制模型对象是通过Clone()方法实现的。其实,这个方法可以是任意名字,比如CloneKey()、CloneMe()等等。不过,一般应该使用Clone()方法,这样做有两个原因:一是出于习惯,复制对象当然应该是Clone();二是,在许多语言中的基础类中,比如作为所有类基础的Object,都定义了Clone()方法。因此,实现原型模式的方法一般应该是:原型类继承了ICloneable接口,在具体原型类中需要时间Clone()方法,完成对象的自我复制。(代码参加下面原型管理器部分)2. 深复制和浅复制
复制有两种:深复制和浅复制。浅复制时,复制对象和原型对象共享对象所有的内部变量,两个对象具有一样的内存空间和生命周期。对原型对象的修改同时也修改了它的复制品,反之亦然。 Object类提供了一个简单的MemberwiseClone()方法来实现浅复制。因为任何一个类的基类都是Object,所以在C#中,实现浅复制非常简单:?
public?class?GateKey?:?Key
????{
????????public?GateKey(string?owner)?:?base("Gate?Key",?owner)?{?}
????????public?override?Key?Clone()
????????{
????????????return?(Key)this.MemberwiseClone();
????????}
????}深复制是指逐个复制原型对象的内部变量,复制对象和原型对象各自具有单独的内存空间和生命周期。复制完成后,他们就是两个完全独立的个体,在逻辑上互不相干。本文实例中的Clone()操作都是深复制的实现。3. 原型管理器
原型对象在原型模式中居于核心的地位。一个系统能产生的对象的种类多少,取决于系统中存在多少原型。当一个系统中原型对象较多或数量不固定,需要能动态的增加或删除的时候,我们就需要提供一个注册表机制,让客户能存储、检索和管理可用原型池——这个注册表称为原型管理器。原型管理器一般是一个关联型存储器,比如哈希表,它包含一些基本操作可以通过关键字来注册、检索、删除原型。具有原型管理器的原型模式如下图所示:
? 示例代码:
namespace?Prototype
{
????//抽象钥匙原型
????[Serializable]
????public?abstract?class?Key?:?ICloneable
????{
????????private?string?name;
?
????????public?string?Name
????????{
????????????get?{?return?name;?}
????????????set?{?name?=?value;?}
????????}
????????private?string?owner;
?
????????public?string?Owner
????????{
????????????get?{?return?owner;?}
????????????set?{?owner?=?value;?}
????????}
????????public?Key(string?name,?string?owner)
????????{
????????????this.name?=?name;
????????????this.owner?=?owner;
????????}
?
????????public?override?String?ToString()
????????{
????????????return?this.Name?+?",?belongs?to?"?+?this.Owner;
????????}
?
????????public?virtual?Object?Clone()
????????{
????????????MemoryStream?memoryStream?=?new?MemoryStream();
????????????BinaryFormatter?formatter?=?new?BinaryFormatter();
????????????formatter.Serialize(memoryStream,?this);
????????????memoryStream.Position?=?0;
????????????return?formatter.Deserialize(memoryStream);
????????}
????}
????//大门钥匙
????[Serializable]
????public?class?GateKey?:?Key
????{
????????public?GateKey(string?owner)?:?base("Gate?Key",?owner)?{?}
????}
????//万能 钥匙?:-)
????[Serializable]
????public?class?GeneralKey?:?Key
????{
????????public?GeneralKey(string?name,?string?owner)?:?base(name,?owner)?{?}
????}
?
????public?class?KeyManager
????{
????????private?System.Collections.Hashtable?keys?=?new?System.Collections.Hashtable();
?
?
????????public?Key?this[string?name]
????????{
????????????set?{?keys.Add(name,?value);?}
????????????get?{?return?(Key)keys[name];?}
????????}
????}
????//客户调用方法
????public?class?Client
????{
????????public?static?void?Main(string[]?args)
????????{
????????????KeyManager?keyManager?=?new?KeyManager();
????????????keyManager["gate"]?=?new?GateKey("GF");
????????????keyManager["key2"]?=?new?GeneralKey("key2",?"GF");
????????????keyManager["key3"]?=?new?GeneralKey("key3",?"GF");
????????????keyManager["key4"]?=?new?GeneralKey("key4",?"GF");
????????????keyManager["key5"]?=?new?GeneralKey("key5",?"GF");
?
????????????Key?newKey?=?(Key)keyManager["key2"].Clone();
????????????newKey.Name?=?"Office";
????????????newKey.Owner?=?"Me";
?
????????????Console.WriteLine(newKey);
????????}
????}
}?三. 原型模式的结构和角色 原型模式的一般结构如下:
Prototype:抽象原型角色,定义一个原型的抽象定义,其中包含一个复制自身的接口。ConcretePrototypeA/B:具体原型角色,作为原型被复制的具体对象,需实现抽象原型所定义的接口。Client:客户调用端,客户使用原型对象复制出需要的对象 包含原型管理器的原型模式的结构如下:
PrototypeManager:原型管理器角色,提供具体原型对象的增加、删除、浏览等管理功能。四. 原型模式总结 原型模式应用于希望系统独立于产品的创建、表示和构成时,这和工厂模式很类似。事实上,和工厂模式相同的是,原型模式同样对客户隐藏了对象的创建工作,但是,与工厂模式通过对一个类进行实例化来构造新对象不同的是,原型模式是通过拷贝一个现有对象生成新对象的。工厂模式适用于产品种类有限的情况下,当产品数量巨大或需要提供动态产品增删等性能时,使用原型模式具有更强的适应性。 原型模式适用于以下情况(GoF):当要实例化的类是在运行时刻指定时,例如,通过动态装载;为了避免创建一个与产品类层次平行的工厂类层次时;当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。