小话设计模式(五)原型模式

来源:互联网 发布:网络售药 京东 编辑:程序博客网 时间:2024/06/05 19:08

原型(Prototype)模式用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

简单举个例子,游戏里面有动物类型,定义如下:

public class AnimalPrototype{public string name { get; set;}public int hp{get;set;}public AnimalPrototype(string name_){name = name_;}public override string ToString(){return string.Format ("[Animal: name={0}, hp={1}]", name, hp);}}

创建时:

AnimalPrototype wolverine = new AnimalPrototype ("wolverine");wolverine.hp = 100;

这里只有两个属性,name和hp,实际情况可能有十几个或者更多的属性(例如怒气值,是否愤怒,是否遭遇过等等)。我们不希望在创建狼獾(wolverine)时反复设置这些属性,或者有时候我们需要创建一个临时的拷贝了所有属性的wolverine(例如预览伤害时,我们需要生成一个临时的拷贝对象,用于计算预览值),这样的情况下,原型模式是一种不错的选择。

稍微修改一下代码,使用.Net为我们提供的ICloneable接口(这个接口经常见到啊!原来还是一种设计模式!突然感觉高贵冷艳了起来。):

public class AnimalPrototype : ICloneable{public string name { get; set;}public int hp{get;set;}public AnimalPrototype(string name_){name = name_;}public override string ToString(){return string.Format ("[Animal: name={0}, hp={1}]", name, hp);}public virtual object Clone(){return (object)this.MemberwiseClone ();}}

使用时:

AnimalPrototype wolverine2 = (AnimalPrototype)wolverine.Clone ();wolverine2.hp = 80;

这样我们就可以进行各种克隆了,生成茫茫多的wolverine。

然而,这并不能满足我们的需求。例如,我们定义了一种会使用技能的动物:

public class AnimalSkill : ICloneable{public string skillName { get; set;}public int skillAttack { get; set;}public object Clone(){return (object)this.MemberwiseClone ();}public override string ToString (){return string.Format ("[AnimalSkill: skillName={0}, skillAttack={1}]", skillName, skillAttack);}}

public class SkillAnimalPrototype : AnimalPrototype{private AnimalSkill _skill;public AnimalSkill skill { get{ return _skill;}}public SkillAnimalPrototype(string name_):base(name_){_skill = new AnimalSkill ();}public void SetSkillInfo(string name_, int attack_){_skill.skillName = name_;_skill.skillAttack = attack_;}public override object Clone(){return (object)this.MemberwiseClone ();}public override string ToString(){return base.ToString () + skill.ToString ();}}

当我们调用时:

SkillAnimalPrototype phoenix = new SkillAnimalPrototype ("phoenix");phoenix.SetSkillInfo ("Revive", 0);SkillAnimalPrototype phoenix2 = (SkillAnimalPrototype)phoenix.Clone ();phoenix2.name = "phoenix2";phoenix2.SetSkillInfo ("Fire", 10);

Console.WriteLine (phoenix);Console.WriteLine (phoenix2);

会得到结果:

[Animal: name=phoenix, hp=0][AnimalSkill: skillName=Fire, skillAttack=10]

[Animal: name=phoenix2, hp=0][AnimalSkill: skillName=Fire, skillAttack=10]

我们看到AnimalSkill都显示的是第二次设置的信息。是因为MemberwiseClone只是浅表复制,复制值类型没有问题,但是复制引用类型的时候,只是复制了引用(对string做了特殊处理),所以两个AnimalSkill指向的是同一个对象。

所以我们需要这样修改SkillAnimalPrototype的Clone方法

public override object Clone(){SkillAnimalPrototype ret = (SkillAnimalPrototype)base.Clone();ret._skill = (AnimalSkill)_skill.Clone ();return ret;}

这样我们就会得到新的结果:

[Animal: name=phoenix, hp=0][AnimalSkill: skillName=Revive, skillAttack=0]

[Animal: name=phoenix2, hp=0][AnimalSkill: skillName=Fire, skillAttack=10]

这样就是深表复制。

深表复制比较复杂,实际情况中要格外注意。而且,并不是所有的引用类型的变量都需要被深表复制,例如我们有一个AnimalBasicData的类型:

public class AnimalBasicData{public int mapHp{ get; private set;}public void LoadFromData(string file){//TODO:}}

通过LoadFromData加载配置文件的数据,而我们希望所有的wolverine都用同一份AnimalBasicData,所有的phoenix都用另一份AnimalBasicData,Clone的时候就不需要也不该对AnimalBasicData进行深表复制。


最后说两句:

原型模式好处在于对于相同(或者大部分相似)的对象,我们创建它们的时候,并不需要考虑每一个对象的每一个细节。并且可以创建一个对象的副本,在有限的范围内修改这个副本不会影响原对象。

坏处就在于,我们需要实现复杂的Clone方法,需要避免多余的深表复制(浪费内存),尤其要注意循环引用的情况(例如A引用B,B再引用A,这样会造成死循环)。

1 0