创建型模式——原型(Prototype)
问题背景
当希望批量生成有状态对象时,应该考虑使用原型模式。假设有一种怪物,它有生命值、魔法值、攻击力、防御力等大量属性,可以一次分裂出十个与自己完全相同的怪物。如果直接实现这个逻辑,代码会是这样的:
var monster = new Monster();
monster.HP = 100;
monster.MP = 50;
monster.ATK = 50;
monster.DEF = 20;
var copies = new Monster[10];
Monster copy;
for (int i = 0; i < copies.Length; i++)
{
copy = new Monster();
copy.HP = monster.HP;
copy.MP = monster.MP;
copy.ATK = monster.ATK;
copy.DEF = monster.DEF;
copies[i] = copy;
}
写了一大坨没啥技术含量的代码,而且指不定就把哪个属性给写丢了,简直是费力不讨好。
解决方案
对于这种内部状态复杂,又经常需要拷贝的对象,考虑增加一个接口ICloneable,这个接口表明该对象是可复制的,并暴露复制接口,该接口以自己为“原型”创建出一个一模一样的对象并返回。类的编写者实现这个接口,就可以让使用者在不完全清楚内部状态的情况下复制该类的实例。使用原型后的程序结构是这样的:
这样一来,Monster类的使用者只需要调用Clone接口就可以完成复制,再也不用写一大坨了!
效果
- 简化了对象的复制操作。
- 很好地隐藏了类内部的细节。
- 可以通过注册原型对象来实现“模板”。
缺陷
由于某些语言有“值类型”和“引用类型”之分,复制行为就会有“浅复制”和“深复制”的区别。在这些语言中,若原型模式使用不当,可能会造成一些匪夷所思的问题,这些问题一般很难被排查出来。
使用原型之前请一定一定一定要确保你知道你正在干什么。
使用原型之前请一定一定一定要确保你知道你正在干什么。
使用原型之前请一定一定一定要确保你知道你正在干什么。
相关模式
- 抽象工厂:原型是抽象工厂的一种实现方式。
- 复合:复合结构的组件一般都会使用原型。
实现
using System;
namespace Prototype
{
class Client
{
public interface ICloneable
{
ICloneable Clone();
}
public class Monster : ICloneable
{
public int HP { get; set; }
public int MP { get; set; }
public int ATK { get; set; }
public int DEF { get; set; }
//...
public ICloneable Clone()
{
var copy = new Monster();
copy.HP = HP;
copy.MP = MP;
copy.ATK = ATK;
copy.DEF = DEF;
return copy;
}
public override string ToString()
{
return $"怪物:[HP: {HP}, MP: {MP}, ATK: {ATK}, DEF: {DEF}]";
}
}
static void Main(string[] args)
{
var monster = new Monster();
monster.HP = 100;
monster.MP = 50;
monster.ATK = 50;
monster.DEF = 20;
Console.WriteLine(monster);
Console.WriteLine("分裂...");
var copies = new Monster[10];
for (int i = 0; i < copies.Length; i++)
{
copies[i] = monster.Clone() as Monster;
Console.WriteLine(copies[i]);
}
}
}
}