您的位置:知识库 » 软件设计

设计模式系列—原型模式

作者: CallHot  来源: 博客园  发布时间: 2010-12-12 20:55  阅读: 5135 次  推荐: 0   原文链接   [收藏]  

  一、上篇回顾

  上篇创建者模式中,我们主要讲述了创建者的几类实现方案,和创建者模式的应用的场景和特点,创建者模式适合创建复杂的对象,并且这些对象的每个组成部分的详细创建步骤可以是动态的变化的,但是每个对象的组装的过程来说可能是相对固定的或者说是对象的创建的过程是固定的,那么通过创建者模式可以很好的解决这类复杂对象的创建,而在我们的生活中或者是项目中可能会有这个方面的需求,那么使用创建者模式无疑是好的选择。

  创建者模式中的每个对象组成部分的构建都是对象本身提供的内部方法,具体的创建者只是调用要创建的对象的内部的相应组成部分的构建方法,组织这些对象内部构建方法的执行顺序,完成对象的完整构建。当我们的客户应用程序需要调用这个创建者时,我们只需要通过指导者调用的形式,提供统一的创建者访问入口,通过构造函数注入或者配置文件的形式来完成创建者的注入。

  二、摘要

  本文主要是讲述创建型模式中一个比较特殊的模式-原型模式,这个模式呢,有个最大的特点是克隆一个现有的对象,这个克隆的结果有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  通过上面的描述,大家应该针对浅复制和深复制的区别有了大概的了解,那么我们再结合程序代码来析吧,可能大家会更熟悉具体的应用。我们先来看看最简单的浅复制和深复制情况,我们这里举例来说吧:

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

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

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

/// <summary>
/// 生产厂家
/// </summary>
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、原型模式的其他情况

  上面讲述了简单的浅复制的情况,那么我们来分析下深复制原型的实现吧,深复制可能考虑的情况相对来说就会比较复杂,因为有可能对象是之间有继承关系或者引用关系的时候,可能我们深复制的时候就需要注意,当然这对我们也是个考验。一般来说深复制一方面可以采用上面我给出的那种简单的深复制对象的时候的方案,还可以通过序列化的形式来进行对象的复制。下面我们来通过序列化的形式来实现原型模式吧:

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

/// <summary>
/// 序列化和反序列化辅助类
/// </summary>
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<T>(string target)
{
return (T)Derializable(target);
}
}
下面给出简单的示例代码,还是使用上面的颜色对象为例。我们修改颜色类中的Clone方法:
#region IColorDemo 成员
public IColorDemo Clone()
{
string target= SerializableHelper.Serializable(this);
return SerializableHelper.Derializable<IColorDemo>(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,肯定二个对象是不同的,通过序列化和反序列化形成新的对象。其实只要是项目中要使用原型模式进行对象复制的情况下,都可以通过序列化的形式来进行深复制。

  七、原型模式使用总结

  原型模式作为创建型模式中的最特殊的一个模式,具体的创建过程,是由对象本身提供,这样我们在很多的场景下,我们可以很方便的快速的构建的对象,就像前面分析讲解的几类场景中,可能我们通过使用对象的克隆,比通过其他几类的创建型模式,效果要好的多,而且代价也小很多。打个比方,原型模式对于系统的扩展,可以做到无缝的扩展,为什么这么说呢?比如其他的创建型工厂,如果新增一个对象类型,那么我们不管是修改配置文件的方式,还是修改代码的形式,无疑我们都是需要进行修改的,对于我们大家通用的公共应用来说这无疑是危险的,那么通过原型模式,则可以解决这样的问题,因为类型本身实现这样的方法即可,但是也有一定的缺点,每个对象都实现这样的方法,无疑是很大的工作量,但是在某些特殊的环境下,或者实际的项目中,可能原型模式是好的选择。

  Demo下载:下载本文Demo

0
0

软件设计热门文章

    软件设计最新文章

      最新新闻

        热门新闻