夸克之书

  • 首页
  • 科普
  • 笔记
  • .NET/C#
  • 物联网
  • 算法
  • Linux
  • 树莓派
夸克之内,别有洞天
  1. 首页
  2. .NET/C#
  3. 正文

23种常见的设计模式(7):原型模式

2020-01-14 5168点热度 0人点赞 0条评论

原型模式是一种很简单也是很常见的一种模式,用一句来概括就是用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

举一个简单的例子,在考试后我们查看各个参考人员的试卷。一般来说,同一考场同一考次的试卷是一样的(别杠,举个例子啦)。那么怎么生成每个人的试卷呢?第一种是每个人new一个各自的试卷对象,如果试卷有千百万份呢,这时候new的性能就不太乐观了。所以有了更好的性能解决方案,首先创建一个人试卷对象,然后直接通过拷贝,在拷贝之后重新设置试卷的分数,姓名,答题答案不就行了吗?

既然是原型模式,肯定有一个或多个类来提供原型。在本案例中,我们来构建一个简单的试卷类。

public class Paper
{
    public string Name { get; set; }

    public int Source { get; set; } = -1;

    public Subject Subject { get; set; }

    public void Info()
    {
        if (string.IsNullOrEmpty(Name) || Source < 0 || Subject == null)
            throw new Exception("啊哈,这张是空白试卷。");
        Console.WriteLine($"{Name}得了{Source}分,题目{Subject.Name}的答案是:{Subject.Answer}");
    }

    /// <summary>
    /// 浅拷贝
    /// </summary>
    /// <returns></returns>
    public Paper ShallowCopy()
    {
        return (Paper)this.MemberwiseClone();
    }

    /// <summary>
    /// 深拷贝
    /// </summary>
    /// <returns></returns>
    public Paper DeepCopy()
    {
        return DeepCopy(this);
    }

    private T DeepCopy<T>(T obj)
    {
        if (obj is string || obj.GetType().IsValueType)
            return obj;
        object retval = Activator.CreateInstance(obj.GetType());
        FieldInfo[] fields = obj.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static);
        foreach (FieldInfo field in fields)
        {
            try { field.SetValue(retval, DeepCopy(field.GetValue(obj))); }
            catch { }
        }
        return (T)retval;
    }
}

/// <summary>
/// 试题
/// </summary>
public class Subject
{
    public string Name
    {
        get
        {
            return "1 + 1 = ?";
        }
    }

    public string Answer { get; set; }
}

可以看到,Paper中包含了试卷人名称,分数以及一道试题。我们通过调用Info方法可以查看试卷的信息,通过调用xxxCopy来进行试卷的复制。

下面是调用场景:

/// <summary>
/// 原型模式
/// </summary>
/// <param name="args"></param>
static void Main(string[] args)
{
    Paper paperXiaoHone = new Paper()
    {
        Name = "小红",
        Source = 90,
        Subject = new Subject()
        {
            Answer = "2",
        }
    };

    Console.WriteLine("=======复制前=========");
    paperXiaoHone.Info();
    //深拷贝
    //Paper paperXiaoming = paperXiaoHone.DeepCopy();
    //浅拷贝
    Paper paperXiaoming = paperXiaoHone.ShallowCopy();
    paperXiaoming.Name = "小明";
    paperXiaoming.Source = 85;
    paperXiaoming.Subject.Answer = "1";

    Console.WriteLine();
    Console.WriteLine("=======复制后=========");
    paperXiaoHone.Info();
    paperXiaoming.Info();

    Console.ReadKey(false);
}

现在来看看小红和小明的试卷信息。

%title插图%num
原型模式demo运行结果

通过上面简单的例子可以看出来,原型模式使用很简单,只需要在模板类中定义一个Copy方法,通过调用Copy方法进行克隆即可。但是在克隆的时候特别需要注意的是对象拷贝分为深拷贝和浅拷贝,浅拷贝是将对象中的所有字段复制到新的对象(副本)中。其中,值类型字段的值被复制到副本中后,在副本中的修改不会影响到源对象对应的值。而引用类型的字段被复制到副本中的是引用类型的引用,而不是引用的对象,在副本中对引用类型的字段值做修改会影响到源对象本身。深拷贝则是创建一个完全独立的副本,无论修改值类型还是引用类型都不会影响源对象本身。

在C#中,浅拷贝可以直接通过Object类中的MemberwiseClone方法进行拷贝。而深拷贝则要复杂一些,需要通过自行重新创建引用对象、序列化或者反射等方式去拷贝源对象。

我们用上面的例子来进行一个简单的测试。首先是浅拷贝:

%title插图%num
浅拷贝

可以看到,在修改小明的试卷答案(Subject对象)后,小红的答案也随之变好了。说明拷贝对象(小明试卷)只拷贝了源对象(小红试卷)中Subject对象的引用地址,导致修改修改Subject对象后,源对象中Subject也随之改变了。虽然在C#中string为引用类型,但从运行结果来看,string在拷贝对象中依然是独立存在的,这是因为string在C#中是不可变数据类型(参考链接3)。

然后再来测试一下深拷贝。

%title插图%num
深拷贝测试

可以看到,通过深拷贝克隆出来的对象是完全独立的一个对象,修改其中的引用类型变量也不会影响到源对象。

注意:浅拷贝和深拷贝中的代码都在上面demo中,这里深拷贝用的方法时反射。

另外,仔细的观众朋友也发现了运行结果中每一句话前面的数字,这是我在构造函数中通过Random生成的试卷编号。可以发现,不管是源对象还是拷贝对象,这个值都没有变化。这也是对象拷贝中需要注意到的地方,拷贝时构造函数不会被执行。这是原型模式的缺点,同时也是原型模式的优点。正是因为这种内存拷贝方式,使得类创建性能大大增强,特别体现在循环中大量创建对象时(不同方式的深拷贝性能不同,有的甚至比new一个新对象的性能还差)。

代码下载:点击下载

参考链接:

1.对象克隆(C# 快速高效率复制对象另一种方式 表达式树转)
2.Object.MemberwiseClone 方法
3.探索c#之不可变数据类型

本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可
标签: 暂无
最后更新:2020-12-13

afirefish

这个人很懒,什么都没留下

打赏 点赞
< 上一篇
下一篇 >

文章评论

您需要 登录 之后才可以评论
放松一下
https://www.quarkbook.com/wp-content/uploads/2021/05/凤凰传奇-海底(Live).flac
分类
  • .NET/C#
  • Linux
  • 树莓派
  • 物联网
  • 科普
  • 笔记
  • 算法
  • 默认
最新 热点 随机
最新 热点 随机
在代码中判断龙芯新旧世界平台 Windows获取固定后缀的IPv6地址 目前为止,你可能找不到第二台支持志强的1L小主机(P350 Tiny+W-1350+ECC+双NVME+PCIE扩展)!!! iKuai(爱快)实现成都移动IPTV IPoE拨号 Linux EXT4分区误删除后数据恢复 C#连接到巴法云
在代码中判断龙芯新旧世界平台
火狐浏览器禁止缓存 23种常见的设计模式(1):单例模式 R86S散热改造 .NET Core Mysql EF主键自增 在VS Code开发Arduino项目 在Windows右键菜单中添加命令提示符
最近评论
Eagle 发布于 8 个月前(10月21日) 参考博主教程成功搞定了成都移动IPTV组播转单播,电脑、手机都可以播放了。但目前有个问题,原IPTV...
rundoze 发布于 10 个月前(08月31日) 牛逼
cc21216695 发布于 2 年前(09月27日) 试了一下,加入启动项也无效,压根没有用
afirefish 发布于 3 年前(11月28日) 非常感谢,非常棒!
》随缘《 发布于 3 年前(11月20日) 最新【一键处理】方法: https://github.com/MrXhh/VSTools/rele...
书签
  • 打赏
  • 毒鸡汤
  • 米店
  • 金鱼直播间

COPYRIGHT © 2023 quarkbook.com. ALL RIGHTS RESERVED.

Theme Kratos Made By Seaton Jiang

蜀ICP备15036129号-9

登录
注册|忘记密码?