C#设计模式四:Prototype(含实例源码)

主程序

static void Main(string[] args)
{
    Stopwatch sw = new Stopwatch();
    sw.Start();
    Enemy enemyPrototype = new FootMan(5, 4, new Location(100, 200));
    GameScene gs = new GameScene();
    List<Enemy> enemyGroup = gs.CreateEnemyGroup(enemyPrototype);
    foreach (FootMan ft in enemyGroup)
    {
        ft.ShowInfo();
        ft.FootmanAttack();
    }

    Console.WriteLine(sw.ElapsedMilliseconds);

    Console.ReadKey();
}

Enemy类

[Serializable]
abstract class Enemy
{
    protected Location location;
    public Location Location
    {
        get { return location; }
        set { location = value; }
    }
    protected int power;
    public int Power
    {
        get { return power; }
        set { power = value; }
    }
    protected int speed;
    public int Speed
    {
        get{return speed; }
        set{ speed =value; }
    }
    public abstract Enemy Clone(bool isDeepCopy);
    public abstract void ShowInfo();
    public Enemy(int power, int speed, Location location)
    {
        Thread.Sleep(1000); // Construct method is assumed to be a high calc work.
        this.power = power; this.speed = speed;
        this.location = location;
    }
}

FootMan类

[Serializable]
class FootMan:Enemy
{
    private string model;
    public FootMan(int power, int speed, Location location):base(power, speed, location)
    {
        model = "footman";
    }
    public override void ShowInfo()
    {
        Console.WriteLine("model:{0} power:{1} speed:{2} location:({3},{4})",
            model,
            power, 
            speed, 
            location.x, 
            location.y
            );
    }
    public override Enemy Clone(bool isDeepCopy)
    {
        FootMan footman;
        if (isDeepCopy)
        {
            MemoryStream memoryStream = new MemoryStream();
            BinaryFormatter formatter = new BinaryFormatter();
            formatter.Serialize(memoryStream, this);
            memoryStream.Position = 0;
            footman = (FootMan)formatter.Deserialize(memoryStream);
        }
        else
            footman = (FootMan)this.MemberwiseClone();
        return footman;
    }

    public void FootmanAttack()
    {
        Console.WriteLine("FootmanAttack");
    }
}

GameScene类

class GameScene
{
    public List<Enemy> CreateEnemyGroup(Enemy enemyPrototype)
    {
        List<Enemy> enemyGroup = new List<Enemy>();
        Enemy e1 = enemyPrototype.Clone(true);
        e1.Location.x = enemyPrototype.Location.x - 10;
        Enemy e2 = enemyPrototype.Clone(true);
        e2.Location.x = enemyPrototype.Location.x + 10;
        Enemy elite = enemyPrototype.Clone(true);
        elite.Power = enemyPrototype.Power *2;
        elite.Speed = enemyPrototype.Speed * 2;
        elite.Location.x = enemyPrototype.Location.x;
        elite.Location.y = enemyPrototype.Location.y + 10;
        enemyGroup.Add(e1);
        enemyGroup.Add(e2);
        enemyGroup.Add(elite);
        return enemyGroup;
    }
}

Location类

[Serializable]
class Location
{
    public int x;
    public int y;
    public Location(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

意图

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

场景

  • 游戏场景中的有很多相似的敌人,它们的技能都一样,但是随着敌人出现的位置不同,这些人的能力不太一样。
    假设,我们现在需要把三个步兵组成一队,其中还有一个精英步兵,能力特别高。那么,你或许可以创建一个敌人抽象类,然后对于不同能力的步兵创建不同的子类。
    然后,使用工厂方法等设计模式让调用方依赖敌人抽象类。
    问题来了,如果有无数种能力不同步兵,难道需要创建无数子类吗?还有,步兵模型的初始化工作是非常耗时的,创建这么多步兵对象可能还会浪费很多时间。
    我们是不是可以通过只创建一个步兵原型, 然后复制出多个一摸一样的步兵呢?复制后, 只需要调整一下这些对象在地图上出现的位置,或者调整一下它们的能力即可。
    原型模式就是用来解决这个问题的。

代码说明

  • Enemy类是抽象原型,它有两个用途,一是定义了原型的一些抽象内容,二是定义了原型模式必须的拷贝方法。
    在这里,我们看到,每个敌人的属性有位置、攻击力、速度等,并且能通过ShowInfo()方法来获取这个人的信息。
  • FootMan类就是具体原型了,它显示了敌人的具体参数以及实现了克隆自身。
  • GameScene类就是调用方,在这里我们并没有看到有和具体原因进行依赖,通过复制传入的克隆原型,得到一些新的敌人,在原型的基础上稍微调整一下就变成了一支敌人部队。
  • 原型模式通过对原型进行克隆来替代无数子类, 因此也就减少了调用方和具体类型产生依赖的程序。
  • Clone()方法接受一个参数,表示是否是深拷贝。在这里,我们通过序列化反序列化实现深拷贝,深拷贝实现对象的完整复制,包括对象内部的引用类型都会复制一份全新的。
    在这里, 如果3个敌人对象的Location都指向内存同一个地址的话, 那么它们就分不开了,因此,在复制的时候需要进行深拷贝,使得它们的Location是独立的。
  • 在初始化Enemy的时候, 我们Sleep()了一下, 目的是模拟对象的创建是一个非常耗时的工作,这也体现了原型模式的另一个优势,在生成敌人的时候,我们其实无需再做这些
    工作了,我们只需要得到它的完整数据,并且进行一些修改就是一个新的敌人。
  • 运行程序后可以看到,虽然创建了三个敌人,但是只耗费了一个敌人的创建时间,三个
    敌人都是从原型克隆出来的。由于进行了深拷贝,修改了一个敌人的位置并不会影响其它敌人。

何时采用

  • 从代码角度来说,如果你希望运行时指定具体类 (比如是使用Footman作为敌人还是使用其它) ,或者你希望避免创建对象时的初始化过程(如果这个过程占用的时间和资源都
    非常多) ,或者是希望避免使用工厂方法来实现多态的时候,可以考虑原型模式。
  • 从应用角度来说, 如果你创建的对象是多变化、多等级的产品,或者产品的创建过程非常耗时的时候(比如,有一定的计算量,或者对象创建时需要从网络或数据库中获取一定的数据);
    或者想把产品的创建独立出去,不想了解产品创建细节的时候可以考虑使用。不得不说,原型模式给了我们多一种创建对象,并且不依赖具体对象的选择。

实现要点

  • .NET中使用Object的MemberwiseClone()方法来实现浅拷贝, 通过序列化和反序列化实现深拷贝,后者代价比较大,选择合适的拷贝方式。
  • 原型模式同样需要抽象类型和具体类型,通过相对稳定的抽象类型来减少或避免客户端的修改可能性。
  • 在代码中, 我们把敌人作为了抽象类型, 抽象层次很高。 完全可以把步兵作为抽象类型,下面有普通步兵,手榴弹步兵等等,再有一个坦克作为抽象类型,下面还有普通坦克和防导弹坦克。
    这样GameScene可能就需要从两种抽象类型克隆出许多步兵和坦克。不管怎么样抽象,只要是对象类型由原型实例所指定,新对象通过原型实例做拷贝,那么这就是原型模式。

注意事项

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

C#设计模式一:Singleton(含实例源码)
C#设计模式二:AbstractFactory(含实例源码)
C#设计模式三:FactoryMethod(含实例源码)
C#设计模式四:Prototype(含实例源码)
C#设计模式五:Builder(含实例源码)
C#设计模式六:Adapter(含实例源码)
C#设计模式七:Facade(含实例源码)
C#设计模式八:Proxy(含实例源码)
C#设计模式九:Flyweight(含实例源码)
C#设计模式十:Composite(含实例源码)
C#设计模式十一:Bridge(含实例源码)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容