设计模式的艺术之道--原型模式

设计模式的艺术之道–原型模式

声明:本系列为刘伟老师博客内容总结(http://blog.csdn.net/lovelion),博客中有完整的设计模式的相关博文,以及作者的出版书籍推荐

本系列内容思路分析借鉴了刘伟老师的博文内容,同时改用C#代码进行代码的演示和分析(Java资料过多 C#表示默哀).

本系列全部源码均在文末地址给出。


本系列开始讲解创建型模式,顾名思义,这类模式主要是为了解决类的创建问题。

  • 创建型模式(Creational Pattern)关注对象的创建过程。分析你怎么来的
  • 创建型模式对类的实例化过程进行了抽象,对用户隐藏了类的实例的创建细节。客户不知道你怎么来的
  • 创建型模式描述如何将对象的创建和使用分离,让用户在使用对象时无须关心对象的创建细节。你用就行,别管我怎么来的。
  • 创建型模式关注点 创建什么(What) 由谁创建(Who) 何时创建(When)
    6种常见的创建型模式

这里写图片描述

原型模式

孙悟空可以用猴毛根据自己的形象,复制(又称“克隆”或“拷贝”)出很多跟自己长得一模一样的“身外身”来。在设计模式中也存在一个类似的模式,可以通过一个原型对象克隆出多个一模一样的对象,该模式称之为原型模式。

1.1定义

  • 原型模式 (Prototype Pattern):使用原型实例指定待创建对象的类型,并且通过复制这个原型来创建新的对象。
  • 通过一个原型来量产其他的产品,抄作业的上学时代,小红做了一份数学作业,张三李四王五都抄了一份,内容都一模一样。
  • 将一个原型对象传给要客户端对象,通过请求原型对象复制自己来实现创建过程
  • 克隆所创建的对象是全新的对象,它们在内存中拥有新的地址,每一个克隆对象都是独立的

1.2情景实例

问题描述
  • 工作周报系统设计
    菜鸟软件公司规模越来越大,职工也越来越多,公司规定每周必须完成周报,但是有些岗位的工作内容比较存在重复性,工作周报内容都大同小异,员工对反复创建和编写工作周报产生了抱怨。周报在在线填写模式,填写完毕直接上传到服务器,不可上传本地文件。现行系统每周默认创建的周报都是空白报表,用户只能通过重新输入或不断复制粘贴来填写重复的周报内容,极大降低了工作效率。如何快速创建相同或者相似的工作周报,成为Sunny公司OA开发人员面临的一个新问题。
初步思路

对工作周报模块进行重新设计和实现:
(1)除了允许用户创建新周报外,还允许用户将创建好的周报保存为模板;
(2)用户在再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一份相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报。

UML类图

这里写图片描述

关键源代码
namespace Prototype
{
    class WeeklyLog:ICloneable
    {
        private String name;
        private String date;
        private String content;
        public void setName(String name)
        {
            this.name = name;
        }
        public void setDate(String date)
        {
            this.date = date;
        }
        public void setContent(String content)
        {
            this.content = content;
        }
        public String getName()
        {
            return (this.name);
        }
        public String getDate()
        {
            return (this.date);
        }
        public String getContent()
        {
            return (this.content);
        }
        //克隆方法clone(),此处使用C#语言提供的克隆机制

        public Object Clone()
        {
            WeeklyLog obj = new Prototype.WeeklyLog ();
            try
            {

                obj.name = this.name;
                obj.date = this.date;
                obj.content = this.content;
                return (Object)obj;
            }
            catch
            {
                Console.WriteLine("不支持复制!");
                return null;
            }
        }
    }
     class Program
    {
        static void Main(string[] args)
        {
            WeeklyLog log_previous = new WeeklyLog();  //创建原型对象
            log_previous.setName("张无忌");
            log_previous.setDate("第12周");
            log_previous.setContent("这周工作很忙,每天加班!");

            Console.WriteLine("****周报****");
            Console.WriteLine("周次:" + log_previous.getDate());
            Console.WriteLine("姓名:" + log_previous.getName());
            Console.WriteLine("内容:" + log_previous.getContent());
            Console.WriteLine("--------------------------------");

            WeeklyLog log_new;
            log_new = (WeeklyLog)log_previous.Clone(); //调用克隆方法创建克隆对象
            log_new.setDate("第13周");
            Console.WriteLine("****周报****");
            Console.WriteLine("周次:" + log_new.getDate());
            Console.WriteLine("姓名:" + log_new.getName());
            Console.WriteLine("内容:" + log_new.getContent());
        }
    }
}
现有缺点(未来变化)
  • 工作进行,发现需要有附件的加载,但是上诉实现并不能实现附件的复制
  • 如何才能实现周报和附件的同时复制呢?继续探讨。

    深克隆与浅克隆

    首先看一下两种克隆方式
    浅克隆(Shallow Clone):当原型对象被复制时,只复制它本身和其中包含的值类型的成员变量,而引用类型的成员变量并没有复制(房子起名字 同一个房子 可以叫茅草屋 也可以叫狗窝 也可以叫皇宫 这几个名字都指向这个房子)
    深克隆(Deep Clone):除了对象本身被复制外,对象所包含的所有成员变量也将被复制(细胞复制,工厂批量盖房子 新的对象与原有对象有相同的的内容 同时占据新的空间)

新的需求UML类图

这里写图片描述

浅克隆分析

在C#语言中,通过覆盖Object类的MemberwiseClone()方法可以实现浅克隆(也可以自己重新写一个),。我们首先使用浅克隆来实现工作周报和附件类的复制,来感受二者的不同区别。

实例关键代码
namespace ShallowClone
{
    class Attachment
    {
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public void Download()
        {
            Console.WriteLine("下载附件,文件名为{0}。", name);
        }
    }
    class WeeklyLog:ICloneable
    {
        private Attachment attachment;
        private string name;
        private string date;
        private string content;

        public Attachment Attachment
        {
            get { return attachment; }
            set { attachment = value; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        public string Date
        {
            get { return date; }
            set { date = value; }
        }
        public string Content
        {
            get { return content; }
            set { content = value; }
        }
        public object Clone()
        {
            return this.MemberwiseClone(); //客户端进行类型转换
        }
        //使用MemberwiseClone()方法实现浅克隆
        //public WeeklyLog Clone()
        //{
        //     return this.MemberwiseClone() as WeeklyLog;
        //}
    }
     class Program
    {
        static void Main(string[] args)
        {
            WeeklyLog log_previous, log_new;
            log_previous = new WeeklyLog();
            Attachment attachment = new Attachment();
            log_previous.Attachment = attachment;
            log_new = (WeeklyLog)log_previous.Clone();
            Console.WriteLine("周报是否相同?{0}", (log_previous == log_new) ? "是" : "否");
            Console.WriteLine("附件是否相同?{0}", (log_previous.Attachment == log_new.Attachment) ? "是" : "否");
            Console.Read();
        }
    }
}

输出结果
这里写图片描述
值类型进行了复制,但是引用类型只是传递了引用

深克隆分析(序列化方式)

深克隆常见的有序列号方式 C#自身还可以用反射进行
序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

实例关键代码
 Clone不同 其他相同 需要复制的内容添加序列化标签
  //使用序列化方式实现深克隆
        public Object Clone()
        {
            Object clone = null;
            FileStream fs = new FileStream("Temp.dat", FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            try
            {
                formatter.Serialize(fs, this);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Failed to serialize. Reason: " + e.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }

            FileStream fs1 = new FileStream("Temp.dat", FileMode.Open);
            BinaryFormatter formatter1 = new BinaryFormatter();
            try
            {
                clone = (WeeklyLog)formatter1.Deserialize(fs1);
            }
            catch (SerializationException e)
            {
                Console.WriteLine("Failed to deserialize. Reason: " + e.Message);
                throw;
            }
            finally
            {
                fs.Close();
            }
            return clone;
        }

输出结果
这里写图片描述

深克隆分析(序列化方式)

利用反射 通过反射拿到引用类型的类型并创建一个实例,将原来的数据赋值给信创建的实例。
纯反射实现,无需实现任何接口,哦对,需要实体类有个无参的构造方法,简单使用强大。

实例关键代码
public Object Copy(object obj)
{
    if (obj == null)
    {
        return null;
    }
    Object targetDeepCopyObj;
    Type targetType = obj.GetType();
    //值类型  
    if (targetType.IsValueType == true)
    {
        targetDeepCopyObj = obj;
    }
    //引用类型   
    else
    {
        targetDeepCopyObj = System.Activator.CreateInstance(targetType);   //创建引用对象   
        System.Reflection.MemberInfo[] memberCollection = obj.GetType().GetMembers();

        foreach (System.Reflection.MemberInfo member in memberCollection)
        {
            //拷贝字段
            if (member.MemberType == System.Reflection.MemberTypes.Field)
            {
                System.Reflection.FieldInfo field = (System.Reflection.FieldInfo)member;
                Object fieldValue = field.GetValue(obj);
                if (fieldValue is ICloneable)
                {
                    field.SetValue(targetDeepCopyObj, (fieldValue as ICloneable).Clone());
                }
                else
                {
                    field.SetValue(targetDeepCopyObj, Copy(fieldValue));
                }

            }
            //拷贝属性
            else if (member.MemberType == System.Reflection.MemberTypes.Property)
            {
                System.Reflection.PropertyInfo myProperty = (System.Reflection.PropertyInfo)member;

                MethodInfo info = myProperty.GetSetMethod(false);
                if (info != null)
                {
                    try
                    {
                        object propertyValue = myProperty.GetValue(obj, null);
                        if (propertyValue is ICloneable)
                        {
                            myProperty.SetValue(targetDeepCopyObj, (propertyValue as ICloneable).Clone(), null);
                        }
                        else
                        {
                            myProperty.SetValue(targetDeepCopyObj, Copy(propertyValue), null);
                        }
                    }
                    catch (System.Exception ex)
                    {

                    }
                }
            }
        }
    }
    return targetDeepCopyObj;
}

public object Clone()
{
    Object obj = Copy(this);
    return obj;
}

1.3模式分析

动机和意图

  • 如何根据自己的形状复制(克隆)出多个身外身?
  • 怎样通过复制一个原型对象得到多个与原型对象一模一样的新对象?

一般结构

  • 原型模式包含三个角色:
  • Prototype(抽象原型类) 提供公有的属性和Clone()方法
  • ConcretePrototype(具体原型类) 继承抽象原型类 使其可以自身复制
  • Client(客户类) 调用原型类进行克隆复制

改进的优点

  • 简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率
  • 扩展性较好,原型改变,克隆对象也直接改变
  • 可以使用深克隆的方式保存对象的状态,增加撤销功能

现存的缺点

  • 需要为每一个类配备一个克隆方法,当对已有的类进行改造时,需要修改源代码,违背了开闭原则
  • 在实现深克隆时需要编写较为复杂的代码

优化空间

  • 原型较多时,比较难以进行管理分辨,可以引入一个原型管理类
  • 原型管理器将多个原型对象存储在一个集合中供客户端使用,可以通过复制集合中对应的原型对象来获得
  • 圆形管理器需要使用单例模式,对象集合通常使用HashTable

适用场景
(1)创建新对象成本较大,新对象可以通过复制已有对象来获得,如果是相似对象,则可以对其成员变量稍作修改
(2) 系统要保存对象的状态,而对象的状态变化很小。
举例:金拱门汉堡 同样的配方同样的味道 煤老板小王去买车,进门看着奥拓86不错 直接大手一挥,给我来一打这个一模一样的。
实例源代码
GitHub地址
百度云地址:链接: https://pan.baidu.com/s/1jIw2Umq 密码: 24zr

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值