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

来源:互联网 发布:智百威软件官网 编辑:程序博客网 时间:2024/05/19 00:17

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

声明:本系列为刘伟老师博客内容总结(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

原创粉丝点击