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

本文详细介绍了原型模式的概念及其在.NET中的实现方式。通过具体的游戏动物类示例,展示了如何利用原型模式简化对象创建过程并实现深浅复制的区别。

原型(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,这样会造成死循环)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值