zoukankan      html  css  js  c++  java
  • 设计模式-原型模式(Prototype)【重点:浅复制与深复制】

    讲故事

    最近重温了一下星爷的《唐伯虎点秋香》,依然让我捧腹不已,幻想着要是我也能有一名秋香如此的侍女,夫复何求呀,带着这个美好的幻想沉沉睡去...

    突然想到,我是一名程序猿呀,想要什么对象不是易如反掌吗,New一个呗,不光是秋香,春、夏、冬都要,身材要超A的,百度好三围(82, 54, 86),开干...

    Coding

    Beauty类,包含美人的属性

        public class Beauty
        {
            public Beauty(int bust, int theWaist, int hipline)
            {
                Bust = bust;
                TheWaist = theWaist;
                Hipline = hipline;
                //模拟产生一名美人的时长
                Thread.Sleep(3000);
            }
    
            /// <summary>
            /// 名称
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 才艺
            /// </summary>
            public string Talent { get; set; }
    
            /// <summary>
            /// 胸围
            /// </summary>
            public int Bust { get; set; }
    
            /// <summary>
            /// 腰围
            /// </summary>
            public int TheWaist { get; set; }
    
            /// <summary>
            /// 臀围
            /// </summary>
            public int Hipline { get; set; }
    
            /// <summary>
            /// 起名
            /// </summary>
            /// <param name="name"></param>
            public void SetName(string name)
            {
                Name = name;
            }
    
            /// <summary>
            /// 设置才艺
            /// </summary>
            /// <param name="talent"></param>
            public void SetTalent(string talent)
            {
                Talent = talent;
            }
        }
    

    客户端生产美女

        internal class Program
        {
            private static void Main(string[] args)
            {
                var sw = new Stopwatch();
                sw.Start();
                var beauty1 = new Beauty(82, 54, 86);
                sw.Stop();
                Console.WriteLine($"生产第一名美人耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty1.SetName("秋香");
                beauty1.SetTalent("弹琴");
    
                sw.Restart();
                var beauty2 = new Beauty(82, 54, 86);
                sw.Stop();
                Console.WriteLine($"生产第二名美人耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty2.SetName("春香");
                beauty2.SetTalent("画画");
    
                sw.Restart();
                var beauty3 = new Beauty(82, 54, 86);
                sw.Stop();
                Console.WriteLine($"生产第三名美人耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty3.SetName("夏香");
                beauty3.SetTalent("舞蹈");
    
                Show(beauty1);
                Show(beauty2);
                Show(beauty3);
    
                Console.WriteLine("
    Happy Ending~");
                Console.ReadLine();
            }
    
            public static void Show(Beauty beauty)
            {
                Console.WriteLine($"我是 {beauty.Name},身材[{beauty.Bust}-{beauty.TheWaist}-{beauty.Hipline}],我的才艺是 {beauty.Talent}");
            }
        }
    

    结果展示:

    生产第一名美女耗时:3008/ms
    
    生产第二名美女耗时:3001/ms
    
    生产第三名美女耗时:3000/ms
    
    我是 秋香,身材[82-54-86],我的才艺是 弹琴
    我是 春香,身材[82-54-86],我的才艺是 画画
    我是 夏香,身材[82-54-86],我的才艺是 舞蹈
    

    我的美人产生了,但就是每次都是通过New创建,设置的标准身材(82-54-86)不变但每次都要设置,而且每次耗时都很长,要是再生产更多,岂不劳累又耗时...

    正在苦恼之时,突然灵感乍现,可以使用原型模式(Prototype)解决呀

    Code Upgrade

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

    其实就是从一个对象再创建另一个可定制的对象,而且不需要知道任何创建的细节

    .NETSystem命名空间中提供了ICloneable接口,其中只有一个方法Clone(),实现这个接口就可以完成原型模式了。

    开始改造:

    Beauty实现ICloneable接口

        public class Beauty : ICloneable
        {
            public Beauty(int bust, int theWaist, int hipline)
            {
                Bust = bust;
                TheWaist = theWaist;
                Hipline = hipline;
                //模拟产生一名美人的时长
                Thread.Sleep(3000);
            }
    
            /// <summary>
            /// 名称
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 才艺
            /// </summary>
            public string Talent { get; set; }
    
            /// <summary>
            /// 胸围
            /// </summary>
            public int Bust { get; set; }
    
            /// <summary>
            /// 腰围
            /// </summary>
            public int TheWaist { get; set; }
    
            /// <summary>
            /// 臀围
            /// </summary>
            public int Hipline { get; set; }
    
            /// <summary>
            /// 起名
            /// </summary>
            /// <param name="name"></param>
            public void SetName(string name)
            {
                Name = name;
            }
    
            /// <summary>
            /// 设置才艺
            /// </summary>
            /// <param name="talent"></param>
            public void SetTalent(string talent)
            {
                Talent = talent;
            }
    
            public object Clone()
            {
                return this.MemberwiseClone();
            }
        }
    

    客户端生产时,除第一个调用new()外,其他使用Clone()方法创建

        internal class Program
        {
            private static void Main(string[] args)
            {
                var sw = new Stopwatch();
                sw.Start();
                var beauty1 = new Beauty(82, 54, 86);
                sw.Stop();
                Console.WriteLine($"生产第一名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty1.SetName("秋香");
                beauty1.SetTalent("弹琴");
    
                sw.Restart();
                var beauty2 = (Beauty)beauty1.Clone();
                sw.Stop();
                Console.WriteLine($"生产第二名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty2.SetName("春香");
                beauty2.SetTalent("画画");
    
                sw.Restart();
                var beauty3 = (Beauty)beauty1.Clone();
                sw.Stop();
                Console.WriteLine($"生产第三名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty3.SetName("夏香");
                beauty3.SetTalent("舞蹈");
    
                Show(beauty1);
                Show(beauty2);
                Show(beauty3);
    
                Console.WriteLine("
    Happy Ending~");
                Console.ReadLine();
            }
    
            public static void Show(Beauty beauty)
            {
                Console.WriteLine($"我是 {beauty.Name},身材[{beauty.Bust}-{beauty.TheWaist}-{beauty.Hipline}],我的才艺是 {beauty.Talent}");
            }
        }
    

    结果展示:

    生产第一名美女耗时:3009/ms
    
    生产第二名美女耗时:0/ms
    
    生产第三名美女耗时:0/ms
    
    我是 秋香,身材[82-54-86],我的才艺是 弹琴
    我是 春香,身材[82-54-86],我的才艺是 画画
    我是 夏香,身材[82-54-86],我的才艺是 舞蹈
    

    我们可以看到,除了第一个创建耗时以外,其他Clone出来的对象基本不耗时,而且不用重复设置固定属性(三围)(一般在初始化的信息不发生变化的情况下,克隆是最好的方法,既隐藏了对象创建的细节,又对性能大大的提高),我心甚欢...

    于是想着对美人的才艺进行升级

    Talent类,对才艺描述,增加才艺段位

        public class Talent
        {
            /// <summary>
            /// 才艺名
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 段位
            /// </summary>
            public int Level { get; set; }
        
    

    Beauty类改造

        public class Beauty : ICloneable
        {
            public Beauty(int bust, int theWaist, int hipline)
            {
                Bust = bust;
                TheWaist = theWaist;
                Hipline = hipline;
                Talent = new Talent();
                //模拟产生一名美人的时长
                Thread.Sleep(3000);
            }
    
            /// <summary>
            /// 名称
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 才艺
            /// </summary>
            public Talent Talent { get; set; }
    
            /// <summary>
            /// 胸围
            /// </summary>
            public int Bust { get; set; }
    
            /// <summary>
            /// 腰围
            /// </summary>
            public int TheWaist { get; set; }
    
            /// <summary>
            /// 臀围
            /// </summary>
            public int Hipline { get; set; }
    
            /// <summary>
            /// 起名
            /// </summary>
            /// <param name="name"></param>
            public void SetName(string name)
            {
                Name = name;
            }
    
            /// <summary>
            /// 设置才艺
            /// </summary>
            /// <param name="talent"></param>
            public void SetTalent(string name, int level)
            {
                Talent.Name = name;
                Talent.Level = level;
            }
    
            public object Clone()
            {
                return this.MemberwiseClone();
            }
        }
    

    客户端设置才艺段位:

        internal class Program
        {
            private static void Main(string[] args)
            {
                var sw = new Stopwatch();
                sw.Start();
                var beauty1 = new Beauty(82, 54, 86);
                sw.Stop();
                Console.WriteLine($"生产第一名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty1.SetName("秋香");
                beauty1.SetTalent("弹琴", 10);
    
                sw.Restart();
                var beauty2 = (Beauty)beauty1.Clone();
                sw.Stop();
                Console.WriteLine($"生产第二名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty2.SetName("春香");
                beauty2.SetTalent("画画", 9);
    
                sw.Restart();
                var beauty3 = (Beauty)beauty1.Clone();
                sw.Stop();
                Console.WriteLine($"生产第三名美女耗时:{sw.ElapsedMilliseconds}/ms
    ");
                beauty3.SetName("夏香");
                beauty3.SetTalent("舞蹈", 8);
    
                Show(beauty1);
                Show(beauty2);
                Show(beauty3);
    
                Console.WriteLine("
    Happy Ending~");
                Console.ReadLine();
            }
    
            public static void Show(Beauty beauty)
            {
                Console.WriteLine($"我是 {beauty.Name},身材[{beauty.Bust}-{beauty.TheWaist}-{beauty.Hipline}],我的才艺是 {beauty.Talent.Name} 段位 {beauty.Talent.Level}");
            }
        }
    

    结果展示:

    生产第一名美女耗时:3022/ms
    
    生产第二名美女耗时:0/ms
    
    生产第三名美女耗时:0/ms
    
    我是 秋香,身材[82-54-86],我的才艺是 舞蹈 段位 8
    我是 春香,身材[82-54-86],我的才艺是 舞蹈 段位 8
    我是 夏香,身材[82-54-86],我的才艺是 舞蹈 段位 8
    

    看到结果我懵了,什么情况,我明明把才艺设置的不一样,怎么三个最后都是 [舞蹈 段位 8] ???

    良久平息之后,才明白导致上面结果的原因,也将是本文的重点:浅复制与深复制

    浅复制与深复制

    浅复制:复制得到的对象的所有变量都包含有与原来的对象相同的值,而所有的对其他对象的引用都仍然指向原来的对象。

    MemberwiseClone()方法就是浅复制,如果字段是值类型的,则对该字段执行逐位复制,如果字段是引用类型,则复制引用但不复制引用对象,因此原始对象及其复本引用同一对象。

    这就是上面,三个美人的才艺最后都变成一样的原因,才艺Talent是一个引用类型,复制三份的是Talent的引用,都指向同一个Talent对象

    深复制:把引用对象的变量指向复制过的新对象,而不是原有的被引用的对象。

    解决上面的问题,我们就要用深复制,要对Talent复制一个新对象,而不是一个引用。('美人'引用了'才艺',假如'才艺'里还有 引用,很多层,就涉及到深复制要深入多少层的问题,需要事先考虑好,而且要当心出现循环引用的问题,本次案例我们就深入到第一层就可以了)

    上代码:

    Talent类,实现ICloneable接口

        public class Talent : ICloneable
        {
            /// <summary>
            /// 才艺名
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 段位
            /// </summary>
            public int Level { get; set; }
    
            public object Clone()
            {
                return this.MemberwiseClone();
            }
        }
    

    Beauty类,增加私有构造函数,以便克隆“才艺”的克隆数据

        public class Beauty : ICloneable
        {
            public Beauty(int bust, int theWaist, int hipline)
            {
                Bust = bust;
                TheWaist = theWaist;
                Hipline = hipline;
                Talent = new Talent();
                //模拟产生一名美人的时长
                Thread.Sleep(3000);
            }
    
            private Beauty(Talent talent)
            {
                this.Talent = (Talent)talent.Clone();
            }
    
            /// <summary>
            /// 名称
            /// </summary>
            public string Name { get; set; }
    
            /// <summary>
            /// 才艺
            /// </summary>
            public Talent Talent { get; set; }
    
            /// <summary>
            /// 胸围
            /// </summary>
            public int Bust { get; set; }
    
            /// <summary>
            /// 腰围
            /// </summary>
            public int TheWaist { get; set; }
    
            /// <summary>
            /// 臀围
            /// </summary>
            public int Hipline { get; set; }
    
            /// <summary>
            /// 起名
            /// </summary>
            /// <param name="name"></param>
            public void SetName(string name)
            {
                Name = name;
            }
    
            /// <summary>
            /// 设置才艺
            /// </summary>
            /// <param name="talent"></param>
            public void SetTalent(string name, int level)
            {
                Talent.Name = name;
                Talent.Level = level;
            }
    
            public object Clone()
            {
                //调用私有构造方法,让“才艺”克隆完成,然后再给这个 “美人” 对象的相关字段赋值,
                //最终返回一个深复制的 “美人” 对象
                var beauty = new Beauty(Talent)
                {
                    Bust = this.Bust,
                    TheWaist = this.TheWaist,
                    Hipline = this.Hipline
                };
                return beauty;
            }
        }
    

    客户端通以前一样,展示结果:

    生产第一名美女耗时:3008/ms
    
    生产第二名美女耗时:0/ms
    
    生产第三名美女耗时:0/ms
    
    我是 秋香,身材[82-54-86],我的才艺是 弹琴 段位 10
    我是 春香,身材[82-54-86],我的才艺是 画画 段位 9
    我是 夏香,身材[82-54-86],我的才艺是 舞蹈 段位 8
    

    看到结果,我心甚喜。突然屋内彩虹环绕,三位美人出现在我面前,婀娜妖娆,不对,怎么少了我的冬香,爬向键盘...

    叮铃铃,叮铃铃...闹铃响了

    握艹,我要续梦~

    源码地址: https://gitee.com/sayook/DesignMode/tree/master/Prototype

  • 相关阅读:
    CCS样式命名
    BFC机制
    html及css小结
    盒模型
    C#函数的使用方法
    如何读代码
    利用CSS-border属性实现圆饼图表
    WNMP环境搭建(win10+Ndinx1.9.15+MySQL5.7.12+PHP5.6.21)
    vue 项目优化:引入cdn使用插件, 减少打包体积
    'PORT' 不是内部或外部命令,也不是可运行的程序
  • 原文地址:https://www.cnblogs.com/sayook/p/12766018.html
Copyright © 2011-2022 走看看