精华内容
下载资源
问答
  • 原型模式谈的最多的就是克隆,谈到克隆我们就会想到第一个克隆羊多利,是我们生物工程史上的一次重大突破。克隆又称作拷贝,记得在做iOS开发的时候,刚接触OC开发谈的比较多一个知识点就是深拷贝和浅拷贝,浅拷贝...

    前言

    原型模式谈的最多的就是克隆,谈到克隆我们就会想到第一个克隆羊多利,是我们生物工程史上的一次重大突破。克隆又称作拷贝,记得在做iOS开发的时候,刚接触OC开发谈的比较多一个知识点就是深拷贝和浅拷贝,浅拷贝只是拷贝了变量的内存地址,深拷贝拷贝了变量的内容。提到克隆我们在Unity开发中最常见的API就是 GameObject.Instantiate(),看他们的注释,Clones the object original and returns the clone,参数就是我们给定的Object然后克隆返回这个对象。为什么要有这个克隆方法呢?假设没有这个克隆方法,也就是说不用原型模式,我们想要实例化10个甚至100个这样的对象,我们是不是都要重复这些操作,实例化模型的顶点,网格,材质等等,创建实例化一个模型是非常费资源的操作,没实例化一个我们就需要从0开始,如果提供克隆方法我们就直接将这段内存copy一份然后返回即可,就方便快捷了许多。在这里抛出一个问题,思考一下Unity的GameObject.Instantiate()是如何实现的?虽然我们看不到C++实现的源代码,我们可以先猜测一下,等到学完本章节,你能猜到答案嘛?

    原型模式存再必要性

    在谈基本介绍之前,先解释一下为什么要存在这样一种设计模式,也就是这个设计模式解决了什么问题。我们在软件开发或者游戏开发中,会存在这样一种情况,我们需要用到多个相同的实例,最简单的办法就是通过多次New来创建多个相同的实例,但是这样存在问题,首先代码上会有很多行相同或者类似的代码,这个是程序员最看不得的,其次是上面提到的创建实例比较耗费资源,创建起来步骤比较繁琐,如果创建大量相同的实例,都是在重复繁琐的创建过程。原型模式就因此诞生,原型模式就是通过现有的实例来实现克隆完成新对象的创建。

    原型模式模式基本介绍

    • 原型模式是指:用原型实例指定创建对象的种类,并且通过拷贝这些原则,创建新的对象。
    • 原型模式是一种创建型设计模式,允许一个对象再创建另外一个可定制的对象,无需知道如何创建的细节。
    • 工作原理是:通过将一个原型对象传给那个要创建的对象,这个要创建的对象通过请求原型对象拷贝它们自己来实施创建对象,即对象Clone()。
    • 形象理解:孙大圣拔出猴毛,变出好多其他"大圣"。
      在这里插入图片描述

    浅拷贝基本介绍

    • 对象语句类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值直接复制一份给新对象。
    • 对于数据类型是引用类型的成员变量,那么浅拷贝会进行引用传递,也就是只会讲改成员变量的引用值(内存地址)复制一份给新的对象,实际上两个对象的该成员变量都指向同一个实例,在这种情况下修改一个对象的变量会影响到另一个对象的该成员变量的值。

    深拷贝基本介绍

    • 赋值对虾干的所有基本数据类型的成员变量值
    • 为所有引用数据类型的成员白能量申请存储空间,并赋值每个引用数据类型成员变量所引用的对象,也就是说对象进行深拷贝要对整个对象进行拷贝。
    • 深拷贝的两种实现方式,1.重写clone方法实现深拷贝,2.通过对象序列化和反序列化实现深拷贝(推荐)

    最常见的深拷贝写法

    如果让我们写一个深拷贝,我们可能信手捏来,下面这种写法也是我们大多数人最常用的写法。

    public class Character
    {
        public int Id { get; set; }
        public string Name { get; set; }
    
        public bool IsAvatar { get; set; }
    
        public Character DeepClone()
        {
            Character character = new Character();
            character.Id = this.Id;
            character.Name = this.Name;
            character.IsAvatar = this.IsAvatar;
            return character;
        }
    }
    

    这种写法比较好理解,但是也有一个很明显的弊端,如果某个类字段特别多,夸张的成百上千,那我们难不成就写成百上千行,如果"勤快"的初级程序还真很有可能写那么多字段赋值,如果稍微高级一点的程序员或许会用反射的方式来字段赋值,针对这种字段比较多的类如何更好的实现深拷贝,请看下面的实现方式,也是推按的实现方式。

    .NetFramework中用到的原型模式

    在.net framework中,童工了ICloneable接口来对对象进行克隆。当然你也可以不去实现ICloneable接口自己直接定义一个Clone()方法,下面我们就来尝试使用一下这个接口来实现深浅拷贝的例子。

    使用ICloneable接口实现克隆

    UML设计图

    在这里插入图片描述

    代码
    [Serializable]
    public class Job
    {
        public int Id { get; set; }
        public string JobName { get; set; }
        public override string ToString()
        {
            return this.JobName;
        }
    }
    
    [Serializable]
    public class Person : ICloneable
    {
        public int Age { get; set; }    //值类型字段
        public string Name { get; set; }    //字符串
        public Job Job { get; set; }        //引用类型字段
        //深拷贝
        public Person DeepClone()
        {
            using (Stream objectStream = new MemoryStream())
            {
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize(objectStream, this);
                objectStream.Seek(0, SeekOrigin.Begin);
                return formatter.Deserialize(objectStream) as Person;
            }
        }
    
        public object Clone()
        {
            return this.MemberwiseClone();//淺拷贝
        }
    
        //浅拷贝
        public Person ShallowClone()
        {
            return this.Clone() as Person;
        }
    }
    
    

    测试

    Person p = new Person() { Name = "P", Age = 21, Job = new Job() { JobName = "Coder", Id = 1001 } };
    Person p1 = p.ShallowClone();
    Person p2 = p.DeepClone();
    string Str = string.Format("修改前:p.Name={0},p.Age={1},p.Job.Id={2},p.Job.JobName={3}", p.Name, p.Age, p.Job.Id, p.Job.JobName);
    Debug.Log(Str);
    Str = string.Format("修改前:p1.Name={0},p1.Age={1},p.Job.Id={2},p1.Job.JobName={3}", p1.Name, p1.Age, p1.Job.Id, p1.Job.JobName);
    Debug.Log(Str);
    Str = string.Format("修改前:p2.Name={0},p2.Age={1},p.Job.Id={2},p2.Job.JobName={3}", p2.Name, p2.Age, p2.Job.Id, p2.Job.JobName);
    Debug.Log(Str);
    
    //修改P1的值
    p1.Name = "PM";
    p1.Age = 30;
    p1.Job.JobName = "Manager";
    p1.Job.Id = 1002;
    
    Str = string.Format("修改后:p.Name={0},p.Age={1},p.Job.Id={2},p.Job.JobName={3}", p.Name, p.Age, p.Job.Id,p.Job.JobName);
    Debug.Log(Str);
    Str = string.Format("修改后:p1.Name={0},p1.Age={1},p1.Job.Id={2},p1.Job.JobName={3}", p1.Name, p1.Age, p1.Job.Id, p1.Job.JobName);
    Debug.Log(Str);
    Str = string.Format("修改后:p2.Name={0},p2.Age={1},p2.Job.Id={2},p2.Job.JobName={3}", p2.Name, p2.Age, p2.Job.Id, p2.Job.JobName);
    Debug.Log(Str);
    
    
    运行效果图:

    在这里插入图片描述

    代码优化

    如果我们项目中有好多需要实现拷贝的Model,那我们是不是每个自定义Model里面都要继承一下ICloneable接口实现一下浅拷贝,再实现一下深拷贝,那多麻烦,程序员最重要的一项技能就是优化代码的能力,只要看到有重复性的代码,那我们就要想办法去抽象和优化,因此就抽象成一个通用的克隆基类,在想要有克隆功能的Model只要继承这个即可,代码如下:

    //通用深拷贝基类
    [Serializable]
    public class BaseClone<T> : ICloneable where T : new()
    {
        //浅拷贝
        public virtual T ShallowClone()
        {
            return (T)this.Clone();
        }
    
        //深拷贝
        public virtual T DeepClone()
        {
            try
            {
                using (Stream memoryStream = new MemoryStream())
                {
                    BinaryFormatter formatter = new BinaryFormatter();
                    formatter.Serialize(memoryStream, this);
                    memoryStream.Position = 0;
                    return (T)formatter.Deserialize(memoryStream);
                }
            }
            catch (Exception ex)
            {
                Debug.LogError("克隆异常:" + ex.ToString());
            }
            return default(T);
        }
    
        public object Clone()
        {
            return this.MemberwiseClone();//淺拷贝
        }
    }
    

    使用

    [Serializable]
    public class Person1 : BaseClone<Person1>
    {
        public int Age { get; set; }
        public string Name { get; set; }
        public Job Job { get; set; }
    }
    

    代码测试还是跟上面一样,具体测试代码可以见给出的案例工程https://github.com/dingxiaowei/UnityDesignPatterns。

    结论

    从上面论证的结果来看,浅拷贝之后的对象跟原来的对象并不是一个对象,但浅表副本复制了原对象的值类型和string类型,但是非string类型的引用类型是复制了引用,也可以理解为复制了数据的地址指针。

    这里有一个特别要注意的一个"坑",也是面试官比较喜欢用来考察面试者的一个点,就是在我们通常理解中,引用类型的浅拷贝在修改拷贝之后的对象的值的时候,是会引起原来初始对象的值的,因为引用类型的浅拷贝只是拷贝了一份引用类型的值的地址指针,但这里要注意字符串类型是一个特例。字符串类型是一个不可修改的引用类型,也就是说string虽然是引用类型,但浅表副本却复制了这个值,把它当值类型一样处理了。

    关于MemberwiseCLone()方法,可以看MSDN上详细的文档说明,它上面注释是这样写的:MemberwiseClone 方法创建一个浅表副本,方法是创建一个新的对象,然后将当前对象的非静态字段复制到新的对象。 如果字段是值类型,则执行字段的逐位副本。 如果字段是引用类型,则会复制引用,但不会复制引用的对象;因此,原始对象及其复本引用相同的对象。

    无论是浅拷贝还是深拷贝,C#都将源对象中所有字段复制到新的对象中。不过,对于值类型字段,引用类型字段以及字符串类型字段的处理,两种拷贝方式存在一定的区别,具体看下面的表:

    字段 拷贝类型 拷贝操作详情 副本或源对象中修改是否相互影响
    值类型 浅拷贝 字段值被拷贝至副本中
    深拷贝 字段被重新创建并赋值
    引用类型 浅拷贝 字段引用被拷贝至副本中
    深拷贝 字段被重新创建并赋值
    字符串 浅拷贝 字段被重新创建并赋值
    (看成值类型即可)
    深拷贝 字段被重新创建并赋值

    解释GameObject.Instantiate原理

    回到我们一开始抛出的问题,关于Unity中如何快速实例化Object的,我们应该知道原理了吧,就是利用深拷贝,
    在这里插入图片描述
    关键在于这个CloneObject的拷贝函数,我们找到这个CloneObject的实现
    在这里插入图片描述
    追本溯源,找到克隆GameObject的地方
    在这里插入图片描述
    想要深入学习源码,可以自己网上找相关资料,声明仅限于学习使用!
    如果对C++指针不太熟悉的,可以看下图回顾一下,也可以看链接&和*的区别
    在这里插入图片描述

    游戏开发中哪儿用到原型模式

    谈了这么多原理和原型模式的介绍,那么我们在游戏开发中哪儿要用到原型模式呢?面试官最喜欢问题的问题不单纯是让你介绍一下原型模式,还要结合具体项目来介绍,那时候就会感觉突然蒙了,在游戏开发中,最常用于属性、角色和对象克隆,例如塔防和rpg或者设计类游戏中的小怪都是一样的属性就很适合用原型模式来创建。下面我们就拿实际商业开发项目举例

    需求

    在回合制RPG中,创建的角色会有各种各样的属性,最简单的比如血量、物理攻击、魔法攻击、行动速度等等,但不同的模型角色都有不同的模板属性,这些属性需要我们配置,我们可以配置在Excel中,然后导成文本数据供程序实例化使用,也有的项目是通过Unity给我们提供的ScriptableObject文件来配置角色的初始属性值,如下图:
    在这里插入图片描述
    关于ScriptableObject不过多介绍,就是unity给我们提供的一种Unity能识别的asset格式的文件,如何自定义这样的文件,看下面的简单代码,只需要继承ScriptableObject类即可,然后就可以鼠标右击创建这样的asset配置文件,上面是实际项目中属性特别多比较复杂,我们用个简单的案例来演示一下,代码如下:

    using System;
    using UnityEngine;
    
    [System.Serializable]
    public class Attributes : ICloneable
    {
        [Tooltip("角色血量")]
        public int hp;
        [Tooltip("物理攻击")]
        public int pAtk;
        [Tooltip("物理防御")]
        public int pDef;
        [Tooltip("魔法攻击")]
        public int mAtk;
        [Tooltip("魔法防御")]
        public int mDef;
        [Tooltip("行动速度")]
        public int spd;
    
        public object Clone()
        {
            return this.MemberwiseClone();
        }
    
        public Person ShallowClone()
        {
            return this.Clone() as Person;
        }
    
        public Attributes DeepClone()
        {
            Attributes result = new Attributes();
            result.hp = hp;
            result.pAtk = pAtk;
            result.pDef = pDef;
            result.mAtk = mAtk;
            result.mDef = mDef;
            result.spd = spd;
            return result;
        }
    }
    
    [CreateAssetMenu(fileName = "CharacterItem", menuName = "(ScriptableObject)CharacterItem")]
    public class CharacterItem : ScriptableObject
    {
        public string Name;
        public string Desc;
        [Tooltip("角色属性")]
        public Attributes Attributes;
    }
    
    

    鼠标右击创建asset配置文件
    在这里插入图片描述
    创建一个火影主人公漩涡鸣人的配置
    在这里插入图片描述

    备注:由于这里字段比较少,所以这里深拷贝就用了最常见的变量赋值的方式,那么我们有了模型属性数据,怎么根据这个配置数据来实例化两个"漩涡鸣人"游戏对象呢,我们不能游戏中一个对象我们就要对应一个asset配置文件吧,如果两个对象一模一样,只需要用一个asset文件即可,但我们需要copy两份这样的属性数据,所以在写了内置的Clone方法来实现,这也是原型模式在游戏开发中的典型应用。

    根据数据模板创建不同的角色对象
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    
    public class CharacterEntity
    {
        public int id;
        public Attributes objectAttributes;
    }
    
    public class PropertyPatternExample2 : MonoBehaviour
    {
        public CharacterItem CharDataModel;
        void Start()
        {
            CharacterEntity char1 = new CharacterEntity() { id = 0, objectAttributes = CharDataModel.Attributes.DeepClone() };
            CharacterEntity char2 = new CharacterEntity() { id = 1, objectAttributes = CharDataModel.Attributes.DeepClone() };
            Debug.Log("修改之前两个角色的属性:");
            Debug.Log("char1 property:" + char1.objectAttributes.ToString());
            Debug.Log("char2 property:" + char2.objectAttributes.ToString());
    
            Debug.Log("修改之后两个角色的属性");
            char1.objectAttributes.hp = 110;
            Debug.Log("char1 property:" + char1.objectAttributes.ToString());
            Debug.Log("char2 property:" + char2.objectAttributes.ToString());
        }
    }
    

    在这里插入图片描述
    上面结果会发现我们根据一个数据模板实例化了两个不同的角色对象,每个对象的属性都是相互独立的,互不影响。

    原型模式注意事项和小结

    • 创建新的对象比较复杂时,可以利用原型模式简化对象的创建过程,同时也能够提高效率。
    • 不用重复初始化对象,而是动态获得对象运行时的状态。
    • 缺点:需要每个类适配一个克隆方法,一般获取浅拷贝就是用MemberwishClone()方法来获取,这对全新的类来说不是很难,但对已有类进行改造时,需要修改其源码,违背了OCP原则,这点需要注意。

    设计模式系列教程汇总

    http://dingxiaowei.cn/tags/设计模式/

    教程代码下载

    https://github.com/dingxiaowei/UnityDesignPatterns

    展开全文
  • 3、原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。 4、产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构 缺点: 1、每个...

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

    优点:

    1、原型模式向客户隐藏了创建新实例的复杂性

    2、原型模式允许动态增加或较少产品类。

    3、原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。

    4、产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构

    缺点:

    1、每个类必须配备一个克隆方法

    2、 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

     

    示例代码

    /*
    原型模式:Prototype这里简写为P,用原型实例指定创建对象的种类,并且通过拷贝这些原型来创建新的对象
    
    抽象原型:Prototype_P 类,
    具体原型:Ferrari_P 类
    
    */
    public abstract class Prototype_P {
        private string id;
        public string Id
        {
            get { return id;}
            private set{ id = value; }
        }
    
        public Prototype_P(string _id)
        {
            this.Id = _id;
            UnityEngine.Debug.Log ("new");//运行可知,得到多个对象,只需被new了一次
        }
    
        public abstract Prototype_P Clone();
    
    }

     

    public class Ferrari_P : Prototype_P {
    
        public Ferrari_P (string _id):base(_id){}
    
        public override Prototype_P Clone ()
        {
            return (Prototype_P)this.MemberwiseClone ();
        }
    }

     

    using UnityEngine;
    
    public class Client_P : MonoBehaviour {
    
        void Start(){
            Prototype_P carPrototype=new Ferrari_P("法拉利");
    
            Prototype_P FerrariClone_1 = carPrototype.Clone ()as Ferrari_P;
            Debug.Log (FerrariClone_1.Id);
    
            Prototype_P FerrariClone_2 = carPrototype.Clone ()as Ferrari_P;
            Debug.Log (FerrariClone_2.Id);
    
            Prototype_P FerrariClone_3 = carPrototype.Clone ()as Ferrari_P;
            Debug.Log (FerrariClone_3.Id);
    
            Prototype_P FerrariClone_4 = carPrototype.Clone ()as Ferrari_P;
            Debug.Log (FerrariClone_4.Id);
    
        }
    }

    在.NET中可以很容易地通过实现ICloneable接口(这个接口就是原型,提供克隆方法,相当于与上面代码中 Prototype_P 抽象类)中Clone()方法来实现原型模式,

    如果我们想我们自定义的类具有克隆的功能,首先定义类继承与ICloneable接口并实现Clone方法。

    转载于:https://www.cnblogs.com/Jason-c/p/8867260.html

    展开全文
  • 3、原型模式详细介绍 3.1、定义 3.2、原型模式结构 3.3、类图实现 3.4、C#举例 3.4.1、情景设定 3.4.2、分析 3.4.3、再次分析 3.5、深复制与浅复制 3.6、深复制使用举例 4、原型模式的优缺点 5、.NET中原型模式...


    1、引言

      我们在软件开发的过程中,常常会用到new字段来创建对象。那么是不是只有这一种办法来创建对象呢?答案显然不是的。然而使用new来创建对象时,适用于任何时候呢?显然答案也是否定的,什么时候就不适用new呢?让我们来看看,当我们创建的一个实例的过程很昂贵或者很复杂,并且需要创建多个这样的类的实例时。如果这时候我们仍然用new操作符去创建这样的类的实例,会导致内存中多分配一个一样的类实例对象,这未免会增加创建类的复杂度和消耗更多的内存空间。那有人会说,采用简单工厂模式来创建这样的系统。伴随着产品类的不断增加,导致子类的数量不断增加,反而增加额系统复杂程度,所以在这里使用共产模式也是来封装类的创建过程也是不合适的。怎么办呢?这里就不得不提到原型模式了。

    2、如何解决

      原型模式可以很好地解决这个问题,因为每个类实例都是相同的,当我们需要多个相同的类实例时,没必要每次都使用new运算符去创建相同的类实例对象,此时我们一般思路就是想——只创建一个类实例对象,如果后面需要更多这样的实例,可以通过对原来对象拷贝一份来完成创建,这样在内存中不需要创建多个相同的类实例,从而减少内存的消耗和达到类实例的复用。 然而这个思路正是原型模式的实现方式。下面就具体介绍下设计模式中的原型设计模式。

    3、原型模式详细介绍

    3.1、原型模式的定义

    原型模式(Prototype Pattern)
      用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。简单来说就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何的创建细节。在现实生活中,也有很多原型设计模式的例子,例如,细胞分裂的过程,一个细胞的有丝分裂产生两个相同的细胞;还有西游记中孙悟空变出后孙的本领和火影忍者中鸣人的隐分身忍术等

    3.2、原型模式结构

      下面是原型模式的类图,我们一起来看看!
    这里写图片描述
    通过上图我们不难发现,在原型模式中只有两个角色:

    • 原型 (原型接口Prototype)角色:声明一个克隆自身的接口;
    • 具体原型(具体ConcreteComponent)角色:该类继承了原型类,用来实现一个克隆自身的操作。

    3.3、类图实现

    具体实现代码如下所示:

    原型类:

    /// <summary>
    /// 抽象类原型
    /// </summary>
    public abstract class Prototype
    {
        private string id;
    
        public string Id
        {
            get { return id; }
        }
    
        public Prototype(string id)
        {
            this.id = id;
        }
    
        /// <summary>
        /// 抽象类的关键就是这个clone()方法
        /// </summary>
        /// <returns></returns>
        public abstract Prototype Clone();
    }

    具体原型类

    /// <summary>
    /// 具体原型
    /// </summary>
    class ConcretePrototype1 : Prototype
    {
        public ConcretePrototype1(string id) : base(id)
        {
        }
    
        /// <summary>
        /// 浅复制
        /// </summary>
        /// <returns></returns>
        public override Prototype Clone()
        {
            //创建当前对象的浅副本
            return (Prototype)this.MemberwiseClone();
        }
    }

    (Prototype)this.MemberwiseClone()创建当前对象的浅副本。方法是创建一个新对象,然后将当前对象的非静态字段复制到该新对象。

    • 如果字段是值类型的,则对该字段执行逐位复制。
    • 如果是引用类型的,则复制引用,但不复制引用的对象;

    因此,原始对象及其副本引用同一对象MSDN

    测试:

    ConcretePrototype1 cp1 = new ConcretePrototype1("cp1...");
    //克隆类ConcretePrototype1的对象cp1就能得到新的实例c1
    ConcretePrototype1 c1 = (ConcretePrototype1)cp1.Clone();
    Console.WriteLine("Cloned:{0}", c1.Id);

    运行结果:

    Cloned:cp1...

    3.4、C#举例

      由于克隆类十分常用,以至于.Net在Syste命名空间中提供了ICloneable接口,其中就只有一个方法Clone(),我们在使用中只需实现这个接口就可以完成原型模式了。下面我们来举例说明:

    3.4.1、情景设定
    • 假设一份简历,我们需要复制三分,简历上显示:姓名、性别、年龄,工作经历,工作经历包括:公司名字和时间区间。

    下面是详细代码:

    /// <summary>
    /// 简历类
    /// </summary>
    public class Resume : ICloneable
    {
        private string name;
        private string sex;
        private string age;
        private string timeArea;
        private string company;
    
        public Resume(string name)
        {
            this.name = name;
        }
    
        /// <summary>
        /// 设置个人信息
        /// </summary>
        /// <param name="sex"></param>
        /// <param name="age"></param>
        public void SetPersonalInfo(string sex, string age)
        {
            this.age = age;
            this.sex = sex;
        }
    
        /// <summary>
        /// 设置工作经历
        /// </summary>
        /// <param name="timeArea"></param>
        /// <param name="company"></param>
        public void SetWorkExperience(string timeArea, string company)
        {
            this.timeArea = timeArea;
            this.company = company;
        }
    
        /// <summary>
        /// 显示
        /// </summary>
        public void Display()
        {
            Console.WriteLine("{0},{1},{2}",name,sex,age);
            Console.WriteLine("{0},{1}",timeArea,company);
        }
    
        public object Clone()
        {
            return (Object)this.MemberwiseClone();
        }
    }

    测试:

    Resume p1 = new Resume("大鸟");
    p1.SetPersonalInfo("男", "28");
    p1.SetWorkExperience("1998-2000", "XX公司");
    
    Resume p2 = (Resume)p1.Clone();
    p2.SetWorkExperience("1999-2002", "YY企业");
    
    Resume p3 = (Resume)p1.Clone();
    p3.SetPersonalInfo("男", "25");
    
    p1.Display();
    p2.Display();
    p3.Display();

    运行结果:

    大鸟,男,28
    1998-2000,XX公司
    大鸟,男,28
    1999-2002,YY企业
    大鸟,男,25
    1998-2000,XX公司
    3.4.2、分析

      通过以上代码来创建对象时,不需要每次都new一次,这样大大的提高了性能。一般情况,在初始化的信息不变的情况下,克隆是最好的办法。这既隐藏了对象创建的细节,又对性能做了很大的提升。但是上面的是值类型的克隆,那么对于复杂的引用类型会不会奏效呢?我们把简历中的工作经历改成一个单独的类,代码修改如下:
    工作经历类:

    /// <summary>
    /// 工作经历
    /// </summary>
    public class WorkExperience
    {
        private string timeArea;
        private string company;
    
        public string TimeArea
        {
            get { return timeArea; }
            set { timeArea = value; }
        }
    
        public string Company
        {
            get { return company; }
            set { company = value; }
        }
    }

    简历类:

    /// <summary>
    /// 简历类
    /// </summary>
    public class Resume : ICloneable
    {
        private string name;
        private string sex;
        private string age;
    
        private WorkExperience work;
    
        public Resume(string name)
        {
            this.name = name;
            work = new WorkExperience();
        }
    
        /// <summary>
        /// 设置个人信息
        /// </summary>
        /// <param name="sex"></param>
        /// <param name="age"></param>
        public void SetPersonalInfo(string sex, string age)
        {
            this.age = age;
            this.sex = sex;
        }
    
        /// <summary>
        /// 设置工作经历
        /// </summary>
        /// <param name="timeArea"></param>
        /// <param name="company"></param>
        public void SetWorkExperience(string timeArea, string company)
        {
            work.TimeArea = timeArea;
            work.Company = company;
        }
    
        /// <summary>
        /// 显示
        /// </summary>
        public void Display()
        {
            Console.WriteLine("{0},{1},{2}",name,sex,age);
            Console.WriteLine("{0},{1}",work.TimeArea,work.Company);
        }
    
        public object Clone()
        {
            return (Object)this.MemberwiseClone();
        }
    }

    测试:

    Resume p1 = new Resume("大鸟");
     p1.SetPersonalInfo("男", "28");
     p1.SetWorkExperience("1998-2000", "XX公司");
    
     Resume p2 = (Resume)p1.Clone();
     p2.SetWorkExperience("1999-2002", "YY企业");
    
     Resume p3 = (Resume)p1.Clone();
     p3.SetPersonalInfo("男", "25");
     p3.SetWorkExperience("1998-2000", "ZZ公司");
    
     p1.Display();
     p2.Display();
     p3.Display();

    运行结果:

    大鸟,男,28
    1998-2000,ZZ公司
    大鸟,男,28
    1998-2000,ZZ公司
    大鸟,男,25
    1998-2000,ZZ公司
    3.4.3、再次分析

      这里你是否想到了刚才说的,复制的情况又两种,值类型和引用类型,引用是不同的,可以点击查看MSDN。这其中提到浅复制,上面第一个例子,可以复制成功是浅复制的原因就是都引用的是值类型。而这里的是引用类型,对引用的对象还是指向了原来的对象,即只引用了地址。所以造成了工作经历都是相同的,而且是最后一个。那么什么是深复制和浅复制呢?下文解释。

    3.5、深复制与浅复制

    • 浅复制:被复制的对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。
    • 深复制:把引用对象的变量指向复制过的新对象,而不是原来的被引用的对象。如:我们刚才的例子,我们希望是不同的p1,p2,p3,复制时一变二,二变三。

    3.6、深复制使用举例

    还是刚才我们改为深复制:
    工作经历类:

    /// <summary>
    /// 工作经历
    /// </summary>
    public class WorkExperience:ICloneable
    {
        private string timeArea;
        private string company;
    
        public string TimeArea
        {
            get { return timeArea; }
            set { timeArea = value; }
        }
    
        public string Company
        {
            get { return company; }
            set { company = value; }
        }
    
        public object Clone()
        {
            return (object)this.MemberwiseClone();
        }
    }

    简历类:

    /// <summary>
    /// 简历类
    /// </summary>
    public class Resume : ICloneable
    {
        private string name;
        private string sex;
        private string age;
    
        private WorkExperience work;
    
        private Resume(WorkExperience work)
        {
            this.work = (WorkExperience)work.Clone();
        }
    
        /// <summary>
        /// 为Clone()方法调用的私有构造函数,以便于克隆“工作经历”的数据
        /// </summary>
        /// <param name="name"></param>
        public Resume(string name)
        {
            this.name = name;
            work = new WorkExperience();
        }
    
        /// <summary>
        /// 设置个人信息
        /// </summary>
        /// <param name="sex"></param>
        /// <param name="age"></param>
        public void SetPersonalInfo(string sex, string age)
        {
            this.age = age;
            this.sex = sex;
        }
    
        /// <summary>
        /// 设置工作经历
        /// </summary>
        /// <param name="timeArea"></param>
        /// <param name="company"></param>
        public void SetWorkExperience(string timeArea, string company)
        {
            work.TimeArea = timeArea;
            work.Company = company;
        }
    
        /// <summary>
        /// 显示
        /// </summary>
        public void Display()
        {
            Console.WriteLine("{0},{1},{2}",name,sex,age);
            Console.WriteLine("{0},{1}",work.TimeArea,work.Company);
        }
    
        /// <summary>
        /// 
        /// </summary>
        /// <returns>最终返回深复制的对象</returns>
        public object Clone()
        {
            //调用私有的构造函数,克隆工作经历,然后再重新给新对象的字段赋值
            Resume obj = new Resume(this.work);
            obj.name = this.name;
            obj.sex = this.sex;
            obj.age = this.age;
    
            return obj;
        }
    }

    测试,我们继续用上面的,运行结果如下:

    大鸟,男,28
    1998-2000,XX公司
    大鸟,男,28
    1999-2002,YY企业
    大鸟,男,25
    1998-2000,ZZ公司

      这次修改是不是达到了预期呢?深复制复制了是新的对象,与原来的对象没有共享关系。

    3.7、原型模式的要点

    • 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
    • 原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,他的原型类Prototype也常用抽象类来替代。
    • 使用原型模式拷贝对象时,需注意浅拷贝与深拷贝的区别。
    • 原型模式可以结合JSON等数据交换格式,为数据模型构建原型。

    4、原型模式的优缺点

    • 优点:

      1. 原型模式向客户隐藏了创建新实例的复杂性
      2. 原型模式允许动态增加或较少产品类。
      3. 原型模式简化了实例的创建结构,工厂方法模式需要有一个与产品类等级结构相同的等级结构,而原型模式不需要这样。
      4. 产品类不需要事先确定产品的等级结构,因为原型模式适用于任何的等级结构
    • 缺点:

      1. 每个类必须配备一个克隆方法
      2. 配备克隆方法需要对类的功能进行通盘考虑,这对于全新的类不是很难,但对于已有的类不一定很容易,特别当一个类引用不支持串行化的间接对象,或者引用含有循环结构的时候。

    5、原型模式的适用场景

    原型模式适用于:

    • 产生对象过程比较复杂,初始化需要许多资源时。
    • 希望框架原型和产生对象分开时。
    • 同一个对象可能会供其他调用者同时调用访问时。

    6、应用举例(unity演示)

      也是写几个类,这里就不在演示了!

    7、总结

      最美好的时光总是短暂的,原型模式的介绍就结束了,原型模式用一个原型对象来指明所要创建的对象类型,然后用复制这个原型对象的方法来创建出更多的同类型对象。它与工厂方法模式的实现非常相似,其中原型模式中的Clone方法就类似工厂方法模式中的工厂方法,只是工厂方法模式的工厂方法是通过new运算符重新创建一个新的对象(相当于原型模式的深拷贝实现),而原型模式是通过调用MemberwiseClone方法来对原来对象进行拷贝,也就是复制,同时在原型模式优点中也介绍了与工厂方法的区别。好了一句话,深复制创建的对象与原对象没有任何共享,一个对象的改变不会影响到另外一个对象;而浅复制是共享的,一个改变了,另外一个对象的成员的值会随之改变。


    The End
      好了,今天的分享就到这里,如有不足之处,还望大家及时指正,随时欢迎探讨交流!!!


    喜欢的朋友们,请帮顶、点赞、评论!您的肯定是我写作的不竭动力!

    相关阅读
    C# 23种设计模式(unity演示)

    展开全文
  • GoF中定义: “使用原型对象来产生指定类的对象,所以产生对象时,是使用复制原型对象来完成。” Unity中 开发者可以组装游戏对象 ...就是一种原型模式的应用。 原型模式原意为: 将一个复杂对...

     

    GoF中定义:

    “使用原型对象来产生指定类的对象,所以产生对象时,是使用复制原型对象来完成。”

     

    Unity中

    开发者可以组装游戏对象

    它可以包括复杂的组件

    组装好了之后,就可以将其存储为Prefab类型的Unity Asset资源

     

    程序代码中使用的实例化方法(GameObject.Instance)

    就是一种原型模式的应用。

     

    原型模式原意为:

    将一个复杂对象的组合方式先行设置好

    后续使用时就不必再经过相同的组装流程

    只需要从做好的“原型”完整地复制出来就可以了。

    转载于:https://www.cnblogs.com/fws94/p/7484657.html

    展开全文
  • 原型模式,顾名思义就是通过对象的原型克隆而产生的新的对象。原型模式在游戏中运用非常多。怪物,一张地图上有许许多多的怪物,这些怪物一般都具有共通性,同一种怪物就有可能拥有同样的模型,同样的攻击方式等。...
  • 实现原型模式 原型模式基本的好处就是对象可以深复制自己,可以很方便有无差错的生成实体,并且把本来大量的类和与之对应的生成类(而且还会随着扩充增加!),缩小成一个原型类,一个生成类,一个数据文件,减少了...
  • 前言:笔者在最开始写程序的时候经常会遇到一种情况,例如更改一个字段、或者添加一种小功能,就要把...如果你也经常遇到这种问题,就说明你现阶段非常需要学习下设计模式了。 在网上经常说的设计模式有23种,也有...
  • 原型 Prototype 对象间数据拷贝(深拷贝,浅拷贝)
  • Unity中Prefab本质就是此模式里的原型,而Spawner要做的只是调用Instantiate方法 新的Prefab被生成以后,通过读取Dragons.txt里配置的信息来设置克隆体的名称和尺寸 注:这里为了快速实现使用txt记录配置表,但...
  • 原型模式 桥接模式 适配器模式 装饰模式 组合模式 享元模式 外观模式 代理模式 模版模式 策略模式 状态模式 观察者模式 备忘录模式 中介者模式 命令模式 访问者模式 职责链模式 迭代器模式 解释...
  • Prototype原型模式在游戏中的应用

    千次阅读 2017-05-17 13:29:20
    笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,...CSDN视频网址:http://edu.csdn.net/lecturer/144 本篇博客给读者介绍一下关于Prototype原型设计模式在游戏中的应用,先举个例子,假设我们为游戏中的每一种怪物
  • 创建型模式:共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。 结构型模式:共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。 行为型模式:共十一种:...
  • 十年磨一剑,作者将设计模式理论巧妙地融入到实践中,以一个游戏的完整实现呈现设计模式的应用及经验的传承 《轩辕剑》之父——蔡明宏、资深游戏制作人——李佳泽、Product Evangelist at Unity Technologies——...

空空如也

空空如也

1 2 3
收藏数 52
精华内容 20
关键字:

unity原型模式