包你懂设计模式之:原型模式(Prototype Pattern)

原型模式属于创建型的设计模式,也是我设计模式系列中最后一个创建型的设计模式,之前的创建型模式有:简单工厂工厂方法抽象工厂建造者模式单例模式,直接点击查看。

原型模式:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的实例。原先实例化对象一般用new关键字,或者用前面的提到的设计模式来获取实例。而原型模式给我们一种前所未有的全新体验,它不用去创建实例,不管实例化的过程多么复杂,完全不关心,只要有一个现成的实例就行,复制粘贴搞定。对象的实例在内存中存储,通过copy对应的这块内存区域能得到新的实例,而且效率非常高,在我们需要大量实例的时候,这个模式能够很好的提升性能

魂斗罗都玩过吧,一场游戏下来杀人(小怪)无数。抽象到我们的代码里面,这些小怪就是一个个小怪对象的实例,传统方式一百个小怪就要实例化100次,如果创建小兵的逻辑复杂或者代价比较高那就更加麻烦了,这种情况下,我们的原型模式就可以大展拳脚了。拿一个小怪作为原型,复制粘贴得到打不完的小怪。

=====================小怪类==========================

/// <summary>
/// 小怪对象     实现ICloneable 接口中就一个Clone方法
/// </summary>
public class Monster : ICloneable 
{
    public Monster()
    {
        Console.WriteLine("我被实例化了。。。。。");
    }
    //编号
    public string MonsterType { get; set; }
    //生命值
    public int HealthValue { get; set; }
    //伤害值
    public int ForceValue { get; set; }

    //攻击
    public void MonsterAttribute()
    {
        Console.WriteLine($"编号:{MonsterType},\t生命值:{HealthValue} ,\t伤害值:{ForceValue} ");
    }
    //实现接口方法,返回一个object对象
    public object Clone()
    {
        //内置的MemberwiseClone()方法,完成对象的拷贝
        return this.MemberwiseClone();
    }
}

 上面代码的核心就是继承了ICloneable 接口并实现了Clone()方法。在客户端我们就可以使用Clone()方法来获取复制后的新对象。

=====================客户端==========================

static void Main(string[] args)
{
    Monster monster = new Monster() {MonsterType = "垃圾小怪",HealthValue = 100,ForceValue=100};
    monster.MonsterAttribute();

    Monster monster1 = (Monster)monster.Clone();
    monster1.MonsterType = "普通小怪";
    monster1.HealthValue = 200;
    monster1.ForceValue = 300;
    monster1.MonsterAttribute();

    Monster monster2 = (Monster)monster.Clone();
    monster2.MonsterType = "进阶怪物";
    monster2.HealthValue = 500;
    monster2.ForceValue = 600;
    monster2.MonsterAttribute();

    Console.ReadLine();
}

=====================结果==========================

从上面运行的结果看来,小怪对象只被实例化了一次,但是我们得到了三个小兵的实例,这就是原型模式的简单应用。但是还没完,原型模式有浅复制和深复制,上面只是浅复制。

MemberwiseClone()方法是这样的:复制的时候如果遇到对象的成员是值类型,则直接把值复制过来,如果字段是引用类型,则复制引用地址,而目标地址上的值不会被复制,因此 复制来的实例 的 引用类型成员 和 原型 的 引用类型成员是指向同一对象的。下面我们通过修改Monster类,来说明下这个情况:

=====================修改后的小怪类==========================

public class Monster : ICloneable 
{
    public Monster()
    {
        Console.WriteLine("我被实例化了。。。。。");
    }
    //编号
    public string MonsterType { get; set; }

    //***********这边将原先的两个字段合并到一个属性类种,产生一个引用类型的字段***************//
    public MonsterAttribute  MonsterAttribute { get; set; }
    //攻击
    public void GetMonsterAttribute()
    {
        Console.WriteLine($"编号:{MonsterType},\t生命值:{MonsterAttribute.HealthValue} ,\t伤害值:{MonsterAttribute.ForceValue} ");
    }
    //实现接口方法,返回一个object对象
    public object Clone()
    {
        //内置的MemberwiseClone()方法,完成对象的拷贝
        return this.MemberwiseClone();
    }
}

上面修改后的代码将原先的两个字段合并到一个属性类里,产生一个引用类型的字段,修改客户端代码:

=====================修改后的客户端==========================

static void Main(string[] args)
{
    Monster monster = new Monster() {
        MonsterType = "垃圾小怪", 
        MonsterAttribute = new MonsterAttribute { HealthValue = 100, ForceValue = 100 }
    };

    Monster monster1 = (Monster)monster.Clone();
    monster1.MonsterType = "普通小怪";
    monster1.MonsterAttribute.HealthValue = 200;
    monster1.MonsterAttribute.ForceValue = 300;

    Monster monster2 = (Monster)monster.Clone();
    monster2.MonsterType = "进阶怪物";
    monster2.MonsterAttribute.HealthValue = 500;
    monster2.MonsterAttribute.ForceValue = 600;

    monster.GetMonsterAttribute();
    monster1.GetMonsterAttribute();
    monster2.GetMonsterAttribute();

    Console.ReadLine();
}

=====================结果==========================

上面的结果可以很好的解释浅复制的弊端,我们从客户端代码来分析下产生上述结果的原因。我们将生命值和伤害值封装到一个新的类中,然后小怪类中加一个字段来存放这个新的类型,这个字段是引用类型,所以我们两次复制后相当于三个小怪的属性都指向了同一个属性对象

那么做的修改也是针对同一个,这就是上面的结果都是最后一次修改后的数值,都被覆盖了。

深复制就是在浅复制的基础上,再把那些引用类型的成员再复制一份,不再和原型有瓜葛。深复制一般有三种方法:

1、让内部的引用类型也实现ICloneable接口,让他也作为一个原型,在复制外层的时候,它也可以被复制。但是这样的话也会有问题,因为对于复杂的类,它内部可能有多个引用类型,还可能嵌套多层,需要在设计的时候就考虑到,否则很麻烦。

2、给他的内部引用类型重新赋值,则相当于重新开辟一块新的地址。这样就不会和原型指向同一引用,但是同样有弊端,如果内部的很复杂呢?当然很简单的这样是没问题的,比如我们今天这个例子:

3、先序列化对象,再反序列化,得到新的完整复制品。弊端是要对象能被序列化。


版权声明:本文为maaici原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。