热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

设计模式系列-原型模式

一、上篇回顾上篇创建者模式中,我们主要讲述了创建者的几类实现方案,和创建者模式的应用的场景和特点,创建者模式适合创建复杂的对象,并且这些对象的每个组成部分的详细创建步骤可以是动态的变化的,但




一、上篇回顾



     上篇创建者模式中,我们主要讲述了创建者的几类实现方案,和创建者模式的应用的场景和特点,创建者模式适合创建复杂的对象,并且这些对象的每


个组成部分的详细创建步骤可以是动态的变化的,但是每个对象的组装的过程来说可能是相对固定的或者说是对象的创建的过程是固定的,那么通过创建者


模式可以很好的解决这类复杂对象的创建,而在我们的生活中或者是项目中可能会有这个方面的需求,那么使用创建者模式无疑是好的选择。


      创建者模式中的每个对象组成部分的构建都是对象本身提供的内部方法,具体的创建者只是调用要创建的对象的内部的相应组成部分的构建方法,组


织这些对象内部构建方法的执行顺序,完成对象的完整构建。当我们的客户应用程序需要调用这个创建者时,我们只需要通过指导者调用的形式,提供统一


的创建者访问入口,通过构造函数注入或者配置文件的形式来完成创建者的注入。



二、摘要



      本文主要是讲述创建型模式中一个比较特殊的模式-原型模式,这个模式呢,有个最大的特点是克隆一个现有的对象,这个克隆的结果有2种,一种是


是浅复制,另一种是深复制,这里我们也会探讨下深复制和浅复制的原理,这样可能更方便大家理解这个原型模式的使用。我们都知道,创建型模式一般是


用来创建一个新的对象,然后我们使用这个对象完成一些对象的操作,我们通过原型模式可以快速的创建一个对象而不需要提供专门的new()操作就可以快


速完成对象的创建,这无疑是一种非常有效的方式,快速的创建一个新的对象。本文将会从以下几个方面进行讲述:


       1、原型模式的使用场景和特点


       2、浅复制和深复制的原理。


       3、举例说明浅复制和深复制。


       4、原型模式的实现方案。


       5、总结原型模式。


     我们这里先给出一个原型模式的原理图:


     image



三、本文大纲



       a、上篇回顾。


       b、摘要。


       c、本文大纲。


       d、原型模式的特点及使用场景。


       e、深复制和浅复制。


       f、原型模式的实现方案。


       g、原型模式使用总结。


       h、系列进度。


       i、下篇预告。



四、原型模式的特点及使用场景



      原型模式的主要思想是基于现有的对象克隆一个新的对象出来,一般是有对象的内部提供克隆的方法,通过该方法返回一个对象的副本,这种创建对


象的方式,相比我们之前说的几类创建型模式还是有区别的,之前的讲述的工厂模式与抽象工厂都是通过工厂封装具体的new操作的过程,返回一个新的对


象,有的时候我们通过这样的创建工厂创建对象不值得,特别是以下的几个场景的时候,可能使用原型模式更简单也效率更高。


      1、如果说我们的对象类型不是刚开始就能确定,而是这个类型是在运行期确定的话,那么我们通过这个类型的对象克隆出一个新的类型更容易。这个


怎么理解。例如我们有的时候在处理DataTable中的记录进行筛选后,放在一个新的DataTable 中,我们知道如果说2个dataTable的架构不同,那么必须


手动的显示的赋值,否则无法使用如下方式进行导入数据:


     下面给出测试的相关代码和说明




   public class DataTableDemo 
   { 
       public void CloneTest() 
       { 
           string cmdText = "SELECT * FROM TABLE"; 
           DataTable dt = new DataTable(); 
           //通过执行上面的cmdText 返回一个dataTable对象;


           //这时候我们可以如下形式复制一个新的dataTable,而不用先创建一个dataTable,然后把每一列都显示的循环添加到新的dataTable中,


           //这是很大的工作量。 
           DataTable dt1 = dt.Clone(); 
           //克隆一个新的对象 dt1.


           #region 不采用克隆的形式复制一个新的dataTable


           DataTable dt2 = new DataTable();


           foreach (DataColumn column in dt.Columns) 
           { 
               dt2.Columns.Add(column.ColumnName); 
           }


           #endregion 
       } 
   }


      2、有的时候我们可能在实际的项目中需要一个对象在某个状态下的副本,这个前提很重要,这点怎么理解呢,例如有的时候我们需要对比一个对象经


过处理后的状态和处理前的状态是否发生过改变,可能我们就需要在执行某段处理之前,克隆这个对象此时状态的副本,然后等执行后的状态进行相应的对


比,这样的应用在项目中也是经常会出现的。


     假设我们有这样的需求,我们在ORM框架的设计中,经常会遇到这样的问题,我们在处理某个对象的编辑状态的时候,我们想框架给我们生成的更新


数据库的SQL语句,不包含数据列没有发生变化的列,不要出现在更新语句中,这个时候,可能一个方案会是,编辑前克隆一个对象,然后等编辑后提交


的时候,生成相应的语句时进行对比之前克隆的对象,看看是否数据发生变化,如果说对象的部分数据列发生变化,那么就只是把变化的数据列进行更新。


      当然上面我只是给出了一种比较简单的,但是效率不是很高的实现方案,还有很多好的方案我就不讨论了,这里只是为了说明原型模式的可用场景。


如果对上面的方式不是很理解或者看文字比较累的话,可以看下面的图,应该就比较清晰了。


image 这就是这种情况下可能原型模式有比较好的表现。


      3、当我们在处理一些对象比较简单,并且对象之间的区别很小,可能只是很固定的几个属性不同的时候,可能我们使用原型模式更合适,例如我们生


活中的彩虹的七彩的颜色,等等,我们只需要根据现有的一个颜色对象,克隆一个新的颜色对象,然后修改具体的颜色的值就可以满足要求,然后如果通过


我们之前讲述的创建型工厂,抽象工厂模式等相对来说就引入新的依赖,并且复杂度也有所提高。例如我们的生活中的颜色的克隆:


image 我们都可以通过红色来克隆其他的所有颜色,只是修改相应的个别属性即可,远比创建一个新的对


象,然后给对象的各个属性赋值来的简单和方便,当然有的时候,如果我们并不需要基于现有的对象复制新的对象,或者我们需要的就是一个干净的空对


象,那么我的首先还是工厂模式或者抽象工厂模式啦。



 


五、深复制和浅复制



      既然我们本篇讲述了原型模式的具体应用,那么我们就必须先搞清楚深复制和浅复制,否则也没有办清楚原型模式中的具体的克隆过程和克隆出来的


对象的详细情况。


     .NET Freamwork 内置的每个继承自System.Object都有保护成员方法:


        // 
        // 摘要: 
        //     创建当前 System.Object 的浅表副本。 
        // 
        // 返回结果: 
        //     当前 System.Object 的浅表副本。 
        [SecuritySafeCritical] 
        protected object MemberwiseClone();


      系统为我们内置提供了复制对象本身的方法,不过这个方法返回的是一个浅复制的对象副本,而且.NET给我提供了一个System.ICloneable的接口,


我们通过实现这个接口,可以为对象提供自定义的克隆方法。


      为了搞明白浅复制和深复制,那么我先要搞懂这2者的区别,.NET本身提供了浅复制的方法,而深复制的方法需要自己实现接口来完成。


我们先来看看浅复制后的对象和对象副本的情况:


      image


我们再看看看深复制的对象和对象副本的情况:


  image 通过上面的描述,大家应该针对浅复制和深复制的区别有了大概的了解,那么我们再结合程序代码来分


析吧,可能大家会更熟悉具体的应用。我们先来看看最简单的浅复制和深复制情况,我们这里举例来说吧:


       我们定义一个杯子类,并且简单定义杯子的几项简单的属性,具体代码如下:


    ///

 
    /// 杯子类 
    ///
 
    public class Cup : ICloneable 
    { 
        private double _rl; 
        private int _height; 
        private Factory _factory; 
        ///  
        /// 高度 
        ///
 
        public int Height 
        { 
            get 
            { 
                return _height; 
            } 
            set 
            { 
                _height = value; 
            } 
        }


        ///

 
        /// 容量 
        ///
 
        public double RL 
        { 
            get 
            { 
                return _rl; 
            } 
            set 
            { 
                _rl = value; 
            } 
        }


        ///

 
        /// 生产厂家 
        ///
 
        public Factory Factory 
        { 
            get 
            { 
                return _factory; 
            } 
            set 
            { 
                _factory = value; 
            } 
        }


        #region ICloneable 成员


        public object Clone() 
        { 
            return this.MemberwiseClone(); 
        }


        #endregion 
    }


具体的测试代码:


class Program 
    { 
        static void Main(string[] args) 
        { 
            Cup cup = new Cup(); 
            cup.Height = 2; 
            Cup cup1 = (Cup)cup.Clone();


            cup1.Height = 1; 
            Console.WriteLine(cup.Height == cup1.Height); 
            System.Threading.Thread.Sleep(10000); 
        } 
    }


运行结果如下:


image


综上所述,我们知道,对于值类型的成员,浅复制也是在副本中重新创建的成员,对应到内存的栈上,分配新的内存空间。那么对于引用类型则因为浅复制


的时候,对象和对象副本共用同一个引用对象,那么不管是在对象还是对象副本中修改了相应的引用成员了之后,那么这个引用类型的成员就会发生变化。


因为2个对象指向同一个内存地址,那么任何一个修改操作都会产生改变。


      那么对于上面的这个类如何修改这个类的实现才能实现深复制呢?


      将上面的Clone方法如下实现:


      public object Clone() 
        { 
            Cup cup = (Cup)this.MemberwiseClone(); 
            Factory factory1 = new Factory(); 
            factory1.FactoryName = this.Factory.FactoryName; 
            cup.Factory = factory1;


            return cup; 
        }


这样就完成了对象的深复制,不管是值类型的成员还是引用类型的成员,这样的对象和对象副本,对任何一个成员属性的修改,都不会影响到改变对象的


值。



六、原型模式的实现方案


      6.1 原型模式的经典实现



    我们先来看看原型模式的经典实现,我们这里已颜色为例来说名下经典实现吧


    定义一个接口, 用来表述所有的颜色对象接口:




    public interface IColorDemo 
    { 
        IColorDemo Clone();


        int Red 
        { 
            get; 
            set; 
        } 
        int Green 
        { 
            get; 
            set; 
        } 
        int Blue 
        { 
            get; 
            set; 
        } 
    }


我们这里给出红色的具体实现代码:


    public class RedColor : IColorDemo 
    { 
        private int red; 
        private int green; 
        private int blue; 
        public int Red 
        { 
            get 
            { 
                return this.red; 
            } 
            set 
            { 
                this.red = value; 
            } 
        } 
        public int Green 
        { 
            get 
            { 
                return this.green; 
            } 
            set 
            { 
                this.green = value; 
            } 
        } 
        public int Blue 
        { 
            get 
            { 
                return this.blue; 
            } 
            set 
            { 
                this.blue = value; 
            } 
        }


        #region IColorDemo 成员


        public IColorDemo Clone() 
        { 
            return (IColorDemo)this.MemberwiseClone(); 
        }


        #endregion 
    }


因为上面的对于颜色,都是通过RGB不同的比例配置出来的,所以我就定义了3个整形的变量,所以我这里只是演示说明。那么具体的测试代码如下:


      static void Main(string[] args) 
       { 
           IColorDemo color = new RedColor(); 
           color.Red = 255;


           IColorDemo color1 = color.Clone(); 
           color1.Blue = 255;


           Console.WriteLine(color.Blue == color1.Blue); 
           System.Threading.Thread.Sleep(10000); 
       }


返回的结果为false。代表对象副本的修改不会影响对象本身的状态。



       6.2、原型模式的其他情况



         上面讲述了简单的浅复制的情况,那么我们来分析下深复制原型的实现吧,深复制可能考虑的情况相对来说就会比较复杂,因为有可能对象是之间


有继承关系或者引用关系的时候,可能我们深复制的时候就需要注意,当然这对我们也是个考验。一般来说深复制一方面可以采用上面我给出的那种简单的


深复制对象的时候的方案,还可以通过序列化的形式来进行对象的复制。下面我们来通过序列化的形式来实现原型模式吧:



         我们先给出序列化和反序列化的帮助类:


         例如我们通过二进制的形式来进行序列化,我们都知道可以序列化的类必须打上标记,标识是否可以序列化,也可以在成员属性上定义。



    ///

 
    /// 序列化和反序列化辅助类 
    ///
 
     public class SerializableHelper 
    { 
         public string Serializable(object target) 
         { 
             using (MemoryStream stream = new MemoryStream()) 
             { 
                 new BinaryFormatter().Serialize(stream, target);


                 return Convert.ToBase64String(stream.ToArray()); 
             } 
         }


         public object Derializable(string target) 
         { 
             byte[] targetArray = Convert.FromBase64String(target);


             using (MemoryStream stream = new MemoryStream(targetArray)) 
             { 
                 return new BinaryFormatter().Deserialize(stream); 
             } 
         }


         public T Derializable(string target) 
         { 
             return (T)Derializable(target); 
         } 
    }


下面给出简单的示例代码,还是使用上面的颜色对象为例。我们修改颜色类中的Clone方法


        #region IColorDemo 成员


        public IColorDemo Clone() 
        { 
            string target= SerializableHelper.Serializable(this); 
            return SerializableHelper.Derializable(target); 
        }


        #endregion


       程序的测试代码如下:


       static void Main(string[] args) 
        { 
            IColorDemo color = new RedColor(); 
            color.Red = 255;


            IColorDemo color1 = color.Clone(); 
            color1.Red = 234;


            Console.WriteLine(color.Blue == color1.Blue); 
            System.Threading.Thread.Sleep(10000); 
        }


        程序的运行结果为false,肯定二个对象是不同的,通过序列化和反序列化形成新的对象。其实只要是项目中要使用原型模式进行对象复制的情况


下,都可以通过序列化的形式来进行深复制。



七、原型模式使用总结



       原型模式作为创建型模式中的最特殊的一个模式,具体的创建过程,是由对象本身提供,这样我们在很多的场景下,我们可以很方便的快速的构建新


的对象,就像前面分析讲解的几类场景中,可能我们通过使用对象的克隆,比通过其他几类的创建型模式,效果要好的多,而且代价也小很多。打个比方,


原型模式对于系统的扩展,可以做到无缝的扩展,为什么这么说呢?比如其他的创建型工厂,如果新增一个对象类型,那么我们不管是修改配置文件的方


式,还是修改代码的形式,无疑我们都是需要进行修改的,对于我们大家通用的公共应用来说这无疑是危险的,那么通过原型模式,则可以解决这样的问


题,因为类型本身实现这样的方法即可,但是也有一定的缺点,每个对象都实现这样的方法,无疑是很大的工作量,但是在某些特殊的环境下,或者实际的


项目中,可能原型模式是好的选择。



八、系列进度



        创建型

        1、系统架构技能之设计模式-单件模式


        2、系统架构技能之设计模式-工厂模式


        3、系统架构技能之设计模式-抽象工厂模式


        4、系统架构技能之设计模式-创建者模式


        5、系统架构技能之设计模式-原型模式


        结构型

        1、系统架构技能之设计模式-组合模式


        2、系统架构技能之设计模式-外观模式


        3、系统架构技能之设计模式-适配器模式


        4、系统架构技能之设计模式-桥模式


        5、系统架构技能之设计模式-装饰模式


        6、系统架构技能之设计模式-享元模式


        7、系统架构技能之设计模式-代理模式


        行为型

        1、系统架构技能之设计模式-命令模式


        2、系统架构技能之设计模式-观察者模式


        3、系统架构技能之设计模式-策略模式


        4、系统架构技能之设计模式-职责模式


        5、系统架构技能之设计模式-模板模式


        6、系统架构技能之设计模式-中介者模式


        7、系统架构技能之设计模式-解释器模式



九、下篇预告



下篇将会针对外观模式进行讲述,该模式也是结构型模式中很有特点设计模式之一,该 模式是将现有系统中的一些细粒度的东西通过外观对象包装起来,


在应用程序中访问这些方法的时候,通过外观类的形式,提供统一的访问入口,并且具体的细节,应用程序并不需要知道,这样就会降低程序调用的复杂


性,由于本人水平有限,不足或者有错误的地方,请大家批评指正,请大家继续支持我,谢谢。



十、Demo下载


       下载本文Demo






推荐阅读
  • MVC框架下使用DataGrid实现时间筛选与枚举填充
    本文介绍如何在ASP.NET MVC项目中利用DataGrid组件增强搜索功能,具体包括使用jQuery UI的DatePicker插件添加时间筛选条件,并通过枚举数据填充下拉列表。 ... [详细]
  • 深入解析Android Activity生命周期
    本文详细探讨了Android中Activity的生命周期,通过实例代码和详细的步骤说明,帮助开发者更好地理解和掌握Activity各个阶段的行为。 ... [详细]
  • 基于OpenCV的小型图像检索系统开发指南
    本文详细介绍了如何利用OpenCV构建一个高效的小型图像检索系统,涵盖从图像特征提取、视觉词汇表构建到图像数据库创建及在线检索的全过程。 ... [详细]
  • 本文详细解析了Java中流的概念,特别是OutputStream和InputStream的区别,并通过实际案例介绍了如何实现Java对象的序列化。文章不仅解释了流的基本概念,还探讨了序列化的重要性和具体实现步骤。 ... [详细]
  • 本文探讨了Java中有效停止线程的多种方法,包括使用标志位、中断机制及处理阻塞I/O操作等,旨在帮助开发者避免使用已废弃的危险方法,确保线程安全和程序稳定性。 ... [详细]
  • 本文提供了多个关键点来帮助开发者提高Java编程能力,包括代码规范、性能优化和最佳实践等方面,旨在指导读者成为更加优秀的Java程序员。 ... [详细]
  • 本文旨在探讨如何撰写高效且全面的工作总结,特别是针对数据库管理、Java编程及Spring框架的学习与应用。文章通过实例分析,帮助读者掌握工作总结的写作技巧,提高个人工作汇报的质量。 ... [详细]
  • SQLite是一种轻量级的关系型数据库管理系统,尽管体积小巧,却能支持高达2TB的数据库容量,每个数据库以单个文件形式存储。本文将详细介绍SQLite在Android开发中的应用,包括其数据存储机制、事务处理方式及数据类型的动态特性。 ... [详细]
  • 本文探讨了Web API 2中特性的路由机制,特别是如何利用它来构建RESTful风格的URI。文章不仅介绍了基本的特性路由使用方法,还详细说明了如何通过特性路由进行API版本控制、HTTP方法的指定、路由前缀的应用以及路由约束的设置。 ... [详细]
  • 本文介绍了多种Eclipse插件,包括XML Schema Infoset Model (XSD)、Graphical Editing Framework (GEF)、Eclipse Modeling Framework (EMF)等,涵盖了从Web开发到图形界面编辑的多个方面。 ... [详细]
  • Nagios可视化插件开发指南 —— 配置详解
    本文详细介绍了Nagios监控系统的配置过程,包括数据库的选择与安装、Nagios插件的安装及配置文件的解析。同时,针对常见的配置错误提供了具体的解决方法。 ... [详细]
  • [编程题] LeetCode上的Dynamic Programming(动态规划)类型的题目
    继上次把backTracking的题目做了一下之后:backTracking,我把LeetCode的动态规划的题目又做了一下,还有几道比较难的Medium的题和Hard的题没做出来,后面会继续 ... [详细]
  • 本教程旨在指导开发者如何在Android应用中通过ViewPager组件实现图片轮播功能,适用于初学者和有一定经验的开发者,帮助提升应用的视觉吸引力。 ... [详细]
  • 本文详细介绍了Oracle RMAN中的增量备份机制,重点解析了差异增量和累积增量备份的概念及其在不同Oracle版本中的实现。通过对比两种备份方式的特点,帮助读者选择合适的备份策略。 ... [详细]
  • 深入解析C++ Atomic编程中的内存顺序
    在多线程环境中,为了防止多个线程同时修改同一数据导致的竞争条件,通常会使用内核级同步对象,如事件、互斥锁和信号量等。然而,这些方法往往伴随着高昂的上下文切换成本。本文将探讨如何利用C++11中的原子操作和内存顺序来优化多线程编程,减少不必要的开销。 ... [详细]
author-avatar
夏乐迎1
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有