设计模式系列:原型模式 Prototype

意图

 

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

场景

 

       游戏场景中的有很多相似的敌人,它们的技能都一样,但是随着敌人出现的位置不同,这些人的能力不太一样。假设,我们现在需要把三个步兵组成一队,其中还有一个精英步兵,能力特别高。那么,你或许可以创建一个敌人抽象类,然后对于不同能力的步兵创建不同的子类。然后,使用工厂方法等设计模式让调用方依赖敌人抽象类。

       问题来了,如果有无数种能力不同步兵,难道需要创建无数子类吗?还有,步兵模型的初始化工作是非常耗时的,创建这么多步兵对象可能还会浪费很多时间。我们是不是可以通过只创建一个步兵原型,然后复制出多个一摸一样的步兵呢?复制后,只需要调整一下这些对象在地图上出现的位置,或者调整一下它们的能力即可。原型模式就是用来解决这个问题的。

示例代码

using System; using System.Collections.Generic; using System.Linq; using System.Text;

//引用命名空间 using System.Threading; using System.Runtime.Serialization.Formatters.Binary; using System.IO;

namespace Prototype_moshi {     class GameScene     {         public List<enemy> create_enemy_group(enemy enemy_prototype)         {             List<enemy> enemy_group = new List<enemy>();             enemy e1 = enemy_prototype.clone(true);             e1.Location._x = enemy_prototype.Location._x - 10;             enemy e2 = enemy_prototype.clone(true);             e2.Location._x = enemy_prototype.Location._x + 10;             enemy elite = enemy_prototype.clone(true);             elite.Power = enemy_prototype.Power * 2;             elite.Speed = enemy_prototype.Speed * 2;             elite.Location._x = enemy_prototype.Location._x;             elite.Location._y = enemy_prototype.Location._y + 10;             enemy_group.Add(e1);             enemy_group.Add(e2);             enemy_group.Add(elite);             return enemy_group;         }     }     [Serializable]     class loaction     {         public int _x;         public int _y;         public loaction(int x, int y)         {             this._x = x;             this._y = y;         }     }     [Serializable]     abstract class enemy     {         private loaction location;         public loaction Location         {             get { return location; }             set { location = value; }         }         private int power;         public int Power         {             get { return power; }             set { power = value; }         }         private int speed;

        public int Speed         {             get { return speed; }             set { speed = value; }

        }         public abstract enemy clone(bool is_deep_copy);//此抽象方法实现的就是实现本身类的复制、经典之处         public abstract void show_info();         public enemy(int power, int speed, loaction location)         {             Thread.Sleep(1000);             this.Power = power;             this.Speed = speed;             this.Location = location;         }

    }     [Serializable]     class foot_man : enemy     {         private string model;         public foot_man(int _power, int _speed, loaction _location)             : base(_power, _speed, _location)         {             model = "footman";         }         public override void show_info()         {             Console.WriteLine("model:{0} power:{1} location:{3}, {4}", model, Power, Speed, Location._x, Location._y);         }         public override enemy clone(bool is_deep_copy)         {             foot_man foot_man;             if (is_deep_copy)             {                 MemoryStream memory_stream = new MemoryStream();                 BinaryFormatter formatter = new BinaryFormatter();                 formatter.Serialize(memory_stream, this); //序列化                 memory_stream.Position = 0;                 foot_man = (foot_man)formatter.Deserialize(memory_stream);  //反序列化             }             else                 foot_man = (foot_man)this.MemberwiseClone();             return foot_man;

        }      
        public void foot_man_attack()         {             Console.WriteLine("Foot_man_attack");         }     } }

代码说明

 

l         Enemy类是抽象原型,它有两个用途,一是定义了原型的一些抽象内容,二是定义了原型模式必须的拷贝方法。在这里,我们看到,每个敌人的属性有位置、攻击力、速度等,并且能通过ShowInfo()方法来获取这个人的信息。

l         FootMan类就是具体原型了,它显示了敌人的具体参数以及实现了克隆自身。

l         GameScene类就是调用方,在这里我们并没有看到有和具体原因进行依赖,通过复制传入的克隆原型,得到一些新的敌人,在原型的基础上稍微调整一下就变成了一支敌人部队。

l         原型模式通过对原型进行克隆来替代无数子类,因此也就减少了调用方和具体类型产生依赖的程序。

l         Clone()方法接受一个参数,表示是否是深拷贝。在这里,我们通过序列化反序列化实现深拷贝,深拷贝实现对象的完整复制,包括对象内部的引用类型都会复制一份全新的。在这里,如果3个敌人对象的Location都指向内存同一个地址的话,那么它们就分不开了,因此,在复制的时候需要进行深拷贝,使得它们的Location是独立的。

l         在初始化Enemy的时候,我们Sleep()了一下,目的是模拟对象的创建是一个非常耗时的工作,这也体现了原型模式的另一个优势,在生成敌人的时候,我们其实无需再做这些工作了,我们只需要得到它的完整数据,并且进行一些修改就是一个新的敌人。

l         运行程序后可以看到,虽然创建了三个敌人,但是只耗费了一个敌人的创建时间,三个敌人都是从原型克隆出来的。由于进行了深拷贝,修改了一个敌人的位置并不会影响其它敌人。

 

何时采用

 

l         从代码角度来说, 如果你希望运行时指定具体类(比如是使用Footman作为敌人还是使用其它),或者你希望避免创建对象时的初始化过程(如果这个过程占用的时间和资源都非常多),或者是希望避免使用工厂方法来实现多态的时候,可以考虑原型模式。

l         从应用角度来说, 如果你创建的对象是多变化、多等级的产品,或者产品的创建过程非常耗时的时候(比如,有一定的计算量,或者对象创建时需要从网络或数据库中获取一定的数据),或者想把产品的创建独立出去,不想了解产品创建细节的时候可以考虑使用。不得不说,原型模式给了我们多一种创建对象,并且不依赖具体对象的选择。

 

实现要点

 

l         .NET中使用Object的MemberwiseClone()方法来实现浅拷贝,通过序列化和反序列化实现深拷贝,后者代价比较大,选择何时的拷贝方式。

l         原型模式同样需要抽象类型和具体类型,通过相对稳定的抽象类型来减少或避免客户端的修改可能性。

l         在代码中,我们把敌人作为了抽象类型,抽象层次很高。完全可以把步兵作为抽象类型,下面有普通步兵,手榴弹步兵等等,再有一个坦克作为抽象类型,下面还有普通坦克和防导弹坦克。这样GameScene可能就需要从两种抽象类型克隆出许多步兵和坦克。不管怎么样抽象,只要是对象类型由原型实例所指定,新对象通过原型实例做拷贝,那么这就是原型模式。

 

注意事项

 

l         注意选择深拷贝和浅拷贝。

l         拷贝原型并进行修改意味着原型需要公开更多的数据,对已有系统实现原型模式可能修改的代价比较大。

 
posted @ 2011-11-16 09:04  指尖流淌  阅读(223)  评论(0编辑  收藏  举报