zoukankan      html  css  js  c++  java
  • [原译]理解并实现原型模式实现ICloneable接口.理解深浅拷贝

    著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

    本文用C#实现原型模式,也会讨论深浅拷贝,已经如何在.net中高效实现ICloneable 接口
    介绍
    有时候我们需要从上下文得到一个对象的拷贝,然后通过一些独立的操作来处理他。原型模式在这种情况下很适用

    GoF 定义原型模式为用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
    Specify the kind of objects to create using a prototypical instance, and create new objects by copying this prototype."

    看一下类图

    主要的参与者有
    • Prototype: 抽象类或接口,定义了方法来拷贝自己
    • ConcretePrototype: 克隆的具体类.
    • Client: 需要执行拷贝对象的软件对象
    然后实现吧

    使用代码

    为了简化。我以一个著名的偷车游戏作为例子
    我们说游戏里有一个注脚。这个主要有着一些定义游戏数据的统计量。保存游戏的时候我们就需要拷贝这个对象,然后序列化到文件中。(仅仅是举个例子,真实的游戏里很少这样做)

    下面这个类抽象类就是概念中的Prototype

    public abstract class AProtagonist
    {
        int m_health;
        int m_felony;
        double m_money;
    
        public int Health
        {
            get { return m_health; }
            set { m_health = value; }
        }
    
        public int Felony
        {
            get { return m_felony; }
            set { m_felony = value; }
        }
    
        public double Money
        {
            get { return m_money; }
            set { m_money = value; }
        }
    
        public abstract AProtagonist Clone();
    }

     

    接口定义了玩家重要的信息,然后定义了一个Clone方法。然后我们定义一个具体的玩家类CJ。这样我们可以克隆当前对象,然后异步的进行序列化

    class CJ : AProtagonist
    {
        public override AProtagonist Clone()
        {
            return this.MemberwiseClone() as AProtagonist;
        }
    }

    这个类就是概念中的ConcretePrototype 。这里为了简化也没有其他一些方法了。

    现在看看客户端软件的写法

    static void Main(string[] args)
    {
        // 演示原型模式
        CJ player = new CJ();
        player.Health = 1;
        player.Felony = 10;
        player.Money = 2.0;
    
        Console.WriteLine("Original Player stats:");
        Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", 
            player.Health.ToString(), 
            player.Felony.ToString(), 
            player.Money.ToString());
    
        // 这里是拷贝部分.
        CJ playerToSave = player.Clone() as CJ;            
    
        Console.WriteLine("\nCopy of player to save on disk:");
        Console.WriteLine("Health: {0}, Felony: {1}, Money: {2}", 
            playerToSave.Health.ToString(), 
            playerToSave.Felony.ToString(), 
            playerToSave.Money.ToString());
    }

    理解深浅拷贝

    上面的代码就模拟了原型模式,有一个问题就是我们使用了MemberwiseCopy 方法。而这个方法是创建浅拷贝的。如果对象里包含引用类型,引用类型的地址就会被从前者拷贝到后者。这样。两个就指向了同样的对象。

    为了说明情况。我们让主角类包含更多的,叫做AdditionalDetails

    public class AdditionalDetails
    {
        int m_charisma;
        int m_fitness;
    
        public int Charisma
        {
            get { return m_charisma; }
            set { m_charisma = value; }
        }
    
        public int Fitness
        {
            get { return m_fitness; }
            set { m_fitness = value; }
        }
    }

     

    然后抽象类将会一个AdditionalDetails 对象

    public abstract class AProtagonistEx
    {
        int m_health;
        int m_felony;
        double m_money;
        // 引用类型
        AdditionalDetails m_details = new AdditionalDetails();
    
        public int Health
        {
            get { return m_health; }
            set { m_health = value; }
        }
    
        public int Felony
        {
            get { return m_felony; }
            set { m_felony = value; }
        }
    
        public double Money
        {
            get { return m_money; }
            set { m_money = value; }
        }
    
        public AdditionalDetails Details
        {
            get { return m_details; }
            set { m_details = value; }
        }
    
        public abstract AProtagonistEx Clone();
    }

     

    具体类既然使用MemberwiseCopy实现拷贝

    class CJEx : AProtagonistEx
    {
        public override AProtagonistEx Clone()
        {
            return this.MemberwiseClone() as AProtagonistEx;
        }
    }
    
     
    
    问题来了。两份拷贝会指向同一个AdditionalDetials 对象
    
    static void Main(string[] args)
    {
        // 演示浅拷贝
        CJEx playerEx = new CJEx();
        playerEx.Health = 1;
        playerEx.Felony = 10;
        playerEx.Money = 2.0;
        playerEx.Details.Fitness = 5;
        playerEx.Details.Charisma = 5;
    
        // 克隆对象然后改变引用类型的值
        CJEx shallowClonedPlayer = playerEx.Clone() as CJEx;
        shallowClonedPlayer.Details.Charisma = 10;
        shallowClonedPlayer.Details.Fitness = 10;
    
        //看看发生了什么
        Console.WriteLine("\nOriginal Object:");
        Console.WriteLine("Charisma: {0}, Fitness: {1}",
            playerEx.Details.Charisma.ToString(),
            playerEx.Details.Fitness.ToString());
        Console.WriteLine("\nShallow Cloned Object:");
        Console.WriteLine("Charisma: {0}, Fitness: {1}",
            shallowClonedPlayer.Details.Charisma.ToString(),
            shallowClonedPlayer.Details.Fitness.ToString());
    }

    画个图。看得清楚些


    为了避免这种期刊。我们需要在堆上创建引用类型的拷贝,然后对应赋值

    class CJEx : AProtagonistEx
    {
        public override AProtagonistEx Clone()
        {
            CJEx cloned = this.MemberwiseClone() as CJEx;
            cloned.Details = new AdditionalDetails();
            cloned.Details.Charisma = this.Details.Charisma;
            cloned.Details.Fitness = this.Details.Fitness;
    
            return cloned as AProtagonistEx;
        }
    }

     

    这样就可以了

    注意。当执行深拷贝的时候,可能引用类型来包括引用类型,因此好的做法是用反射。并且递归的拷贝。直到没有引用类型,具体可参考另一文

    实现ICloneable接口
    ICloneable接口提供了定义拷贝方法的接口。我们可以使用ICloneable 作为概念中的Prototype,我们看一下如果实现ConcretePrototype

    public class CJFinal : ICloneable
    {
        int m_health;
        int m_felony;
        double m_money;
        AdditionalDetails m_details = new AdditionalDetails();
    
        public int Health
        {
            get { return m_health; }
            set { m_health = value; }
        }
    
        public int Felony
        {
            get { return m_felony; }
            set { m_felony = value; }
        }
    
        public double Money
        {
            get { return m_money; }
            set { m_money = value; }
        }
    
        public AdditionalDetails Details
        {
            get { return m_details; }
            set { m_details = value; }
        }
    
        private object ShallowCopy()
        {
            return this.MemberwiseClone();
        }
    
        private object DeepCopy()
        {
            CJFinal cloned = this.MemberwiseClone() as CJFinal;
            cloned.Details = new AdditionalDetails();
            cloned.Details.Charisma = this.Details.Charisma;
            cloned.Details.Fitness = this.Details.Fitness;
    
            return cloned;
        }
    
        #region ICloneable Members
    
        public object Clone()
        {
            return DeepCopy();
        }
    
        #endregion
    }

     

    客户端代码如下

    private static void ICloneableVersionCopy()
    {
        // 演示深拷贝
        CJFinal player = new CJFinal();
        player.Health = 1;
        player.Felony = 10;
        player.Money = 2.0;
        player.Details.Fitness = 5;
        player.Details.Charisma = 5;
    
        // 演示深拷贝
        CJFinal clonedPlayer = player.Clone() as CJFinal;
        clonedPlayer.Details.Charisma = 10;
        clonedPlayer.Details.Fitness = 10;
    
        // 看看效果
        Console.WriteLine("\nOriginal Object:");
        Console.WriteLine("Charisma: {0}, Fitness: {1}",
            player.Details.Charisma.ToString(),
            player.Details.Fitness.ToString());
        Console.WriteLine("\nICloneable Deep Cloned Object:");
        Console.WriteLine("Charisma: {0}, Fitness: {1}",
            clonedPlayer.Details.Charisma.ToString(),
            clonedPlayer.Details.Fitness.ToString());
    }

    Demo下载
    PrototypeDemo.zip

    原文地址:Understanding-and-Implementing-Prototype-Pattern-i

    著作权声明:本文由http://leaver.me 翻译,欢迎转载分享。请尊重作者劳动,转载时保留该声明和作者博客链接,谢谢!

  • 相关阅读:
    Python实现二叉树的遍历
    Selenium模拟浏览器初识
    sklearn中的随机森林
    scrapy爬虫事件以及数据保存为txt,json,mysql
    mac安装mysql及终端操作mysql与pycharm的数据库可视化
    爬虫框架scrapy的基本内容
    多进程实例——爬取百度贴吧
    Python多进程并发操作进程池Pool
    【转】数据库设计三大范式
    【转】栈和堆的区别
  • 原文地址:https://www.cnblogs.com/lazycoding/p/2732554.html
Copyright © 2011-2022 走看看