您的位置:首页 > 其它

设计模式之原型模式-对象的克隆

2016-09-24 17:54 351 查看
一、定义:

使用原型实例对象指定创建对象的种类,并且通过克隆这些原型对象来创建新的对象。

二、原理:

将一个原型对象传给那个要发动创建的对象,这个要发动创建的对象通过请求原型对象克隆自己来实现创建过程。

需要注意的是通过克隆方法所创建的对象是全新的对象,它们在内存中拥有新的地址,通常对克隆所产生的对象进行修改对原型对象不会造成任何影响,每一个克隆对象都是相互独立的。通过不同的方式修改可以得到一系列相似但不完全相同的对象。

三、原型模式结构图:



在原型模式结构图中包含如下几个角色:

●Prototype(抽象原型类):它是声明克隆方法的接口,是所有具体原型类的公共父类,可以是抽象类也可以是接口,甚至还可以是具体实现类。

● ConcretePrototype(具体原型类):它实现在抽象原型类中声明的克隆方法,在克隆方法中返回自己的一个克隆对象。

● Client(客户类):让一个原型对象克隆自身从而创建一个新的对象,在客户类中只需要直接实例化或通过工厂方法等方式创建一个原型对象,再通过调用该对象的克隆方法即可得到多个相同的对象。由于客户类针对抽象原型类Prototype编程,因此用户可以根据需要选择具体原型类,系统具有较好的可扩展性,增加或更换具体原型类都很方便。

四、克隆方法的实现:

两种实现方法

1、通用克隆方法:

通用的克隆实现方法是在具体原型类的克隆方法中实例化一个与自身类型相同的对象并将其返回,并将相关的参数传入新创建的对象中,保证它们的成员属性相同。示意代码如下所示:

public interface Prototype
{
string Name { get; set; }
Prototype Clone();
}
public class ConcretePrototype : Prototype
{
public string Name { get; set; }
public Prototype Clone()
{
Prototype prototype = new ConcretePrototype() {
Name = this.Name
};
return prototype;
}
}
}
static void Main(string[] args)
{
Prototype obj1 = new ConcretePrototype();
obj1.Name = "Zhangsan";
Console.WriteLine(obj1.Name);

Prototype obj2 = obj1.Clone();
Console.WriteLine(obj2.Name);

obj2.Name = "Lisi";
Console.WriteLine(obj1.Name);
Console.WriteLine(obj2.Name);
Console.ReadLine();
}


2、C#中的浅克隆方法:MemberwiseClone()

public class ConcretePrototype : ICloneable//必须实现ICloneable接口
{
public string Name { get; set; }
public object Clone()
{
return this.MemberwiseClone();
}
}
五、案例:

某软件公司一直使用自行开发的一套OA (Office Automatic,办公自动化)系统进行日常工作办理,但在使用过程中,越来越多的人对工作周报的创建和编写模块产生了抱怨。追其原因,软件公司的OA管理员发现,由于某些岗位每周工作存在重复性,工作周报内容都大同小异,如图工作周报示意图。这些周报只有一些小地方存在差异,但是现行系统每周默认创建的周报都是空白报表,用户只能通过重新输入或不断复制粘贴来填写重复的周报内容,极大降低了工作效率,浪费宝贵的时间。如何快速创建相同或者相似的工作周报,成为公司OA开发人员面临的一个新问题。



公司开发人员决定使用原型模式来实现工作周报的快速创建,快速创建工作周报结构图如图:



WeeklyLog充当具体原型类,Object类充当抽象原型类,clone()方法为原型方法。WeeklyLog类的代码如下所示

class WeeklyLog : ICloneable
{
public string Name { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public WeeklyLog ShallowClone()
{
try
{
return (WeeklyLog)this.Clone();
}
catch (Exception ex)
{
Console.WriteLine("Can't support copy!");
return null;
}
}
public object Clone()
{
return this.MemberwiseClone();
}
}


static void Main(string[] args)
{
WeeklyLog log_previous = new WeeklyLog();  //创建原型对象
log_previous.Name = "prevName";
log_previous.Date = "12";
log_previous.Content = "content1";
Console.WriteLine("Date "+log_previous.Date);
Console.WriteLine("Name " + log_previous.Name);
Console.WriteLine("Content " + log_previous.Content);

WeeklyLog log_new;
log_new = log_previous.ShallowClone(); //调用克隆方法创建克隆对象
log_new.Date = "13";
Console.WriteLine("Date " + log_new.Date);
Console.WriteLine("Name " + log_new.Name);
Console.WriteLine("Content " + log_new.Content);

Console.ReadLine();
}


六、带附件的周报:

通过引入原型模式,某软件公司OA系统支持工作周报的快速克隆,极大提高了工作周报的编写效率,受到员工的一致好评。但有员工又发现一个问题,有些工作周报带有附件,例如经理助理“小龙女”的周报通常附有本周项目进展报告汇总表、本周客户反馈信息汇总表等,如果使用上述原型模式来复制周报,周报虽然可以复制,但是周报的附件并不能复制,这是由于什么原因导致的呢?如何才能实现周报和附件的同时复制呢?我们在本节将讨论如何解决这些问题。

在回答这些问题之前,先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)和深克隆(DeepClone)。在C#语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。

见http://blog.csdn.net/zhulongxi/article/details/52640765。

使用深克隆来解决附件的复制问题,深克隆技术实现了原型对象和克隆对象的完全独立,对任意克隆对象的修改都不会给其他对象产生影响,是一种更为理想的克隆实现方式。

七、原型管理器的引入和实现:

原型管理器(Prototype Manager)是将多个原型对象存储在一个集合中供客户端使用,它是一个专门负责克隆对象的工厂,其中定义了一个集合用于存储原型对象,如果需要某个原型对象的一个克隆,可以通过复制集合中对应的原型对象来获得。在原型管理器中针对抽象原型类进行编程,以便扩展:



案例:

某软件公司在日常办公中有许多公文需要创建、递交和审批,例如《可行性分析报告》、《立项建议书》、《软件需求规格说明书》、《项目进展报告》等,为了提高工作效率,在OA系统中为各类公文均创建了模板,用户可以通过这些模板快速创建新的公文,这些公文模板需要统一进行管理,系统根据用户请求的不同生成不同的新公文。



class Program
{
static void Main(string[] args)
{
PrototypeManager pm = PrototypeManager.GetPrototypeManager();
OfficialDocument doc1, doc2, doc3, doc4;
doc1 = pm.GetDocument("far");
doc1.Display();

doc2 = pm.GetDocument("far");
doc2.Display();

Console.WriteLine(doc1==doc2);
doc3 = pm.GetDocument("srs");
doc3.Display();

doc4 = pm.GetDocument("srs");
doc4.Display();
Console.WriteLine(doc3==doc4);
Console.ReadLine();
}
}
public abstract class OfficialDocument : ICloneable
{
public object Clone()
{
return this.MemberwiseClone();
}

public virtual OfficialDocument ShallowClone()
{
return (OfficialDocument)Clone();
}
public virtual void Display()
{

}
}
public class FAR : OfficialDocument
{
public override void Display()
{
Console.WriteLine("FAR");
}
}
public class SRS : OfficialDocument
{
public override void Display()
{
Console.WriteLine("SRS  ");
}
}

public class PrototypeManager
{
private Hashtable ht = new Hashtable();
private static PrototypeManager pm = new PrototypeManager();
private PrototypeManager()
{
ht.Add("far", new FAR());
ht.Add("srs", new SRS());
}

//增加新的document
public void AddDocument(string key, OfficialDocument document)
{
ht.Add(key, document);
}
public OfficialDocument GetDocument(string key)
{
return ((OfficialDocument)ht[key]).ShallowClone();
}
public static PrototypeManager GetPrototypeManager()
{
return pm;
}
}


在PrototypeManager中定义了一个Hashtable类型的集合对象,使用“键值对”来存储原型对象,客户端可以通过Key(如“far”或“srs”)来获取对应原型对象的克隆对象。PrototypeManager类提供了类似工厂方法的getOfficialDocument()方法用于返回一个克隆对象。在本实例代码中,我们将PrototypeManager设计为单例类,使用饿汉式单例实现,确保系统中有且仅有一个PrototypeManager对象,有利于节省系统资源,并可以更好地对原型管理器对象进行控制。

八、总结:

原型模式作为一种快速创建大量相同或相似对象的方式,在软件开发中应用较为广泛,很多软件提供的复制(Ctrl + C)和粘贴(Ctrl + V)操作就是原型模式的典型应用,下面对该模式的使用效果和适用情况进行简单的总结。

1.主要优点

原型模式的主要优点如下:

(1) 当创建新的对象实例较为复杂时,使用原型模式可以简化对象的创建过程,通过复制一个已有实例可以提高新实例的创建效率。

(2) 扩展性较好,由于在原型模式中提供了抽象原型类,在客户端可以针对抽象原型类进行编程,而将具体原型类写在配置文件中,增加或减少产品类对原有系统都没有任何影响。

(3) 原型模式提供了简化的创建结构,工厂方法模式常常需要有一个与产品类等级结构相同的工厂等级结构,而原型模式就不需要这样,原型模式中产品的复制是通过封装在原型类中的克隆方法实现的,无须专门的工厂类来创建产品。

(4) 可以使用深克隆的方式保存对象的状态,使用原型模式将对象复制一份并将其状态保存起来,以便在需要的时候使用(如恢复到某一历史状态),可辅助实现撤销操作。

2.主要缺点

原型模式的主要缺点如下:

(1) 需要为每一个类配备一个克隆方法,而且该克隆方法位于一个类的内部,当对已有的类进行改造时,需要修改源代码,违背了“开闭原则”。

(2) 在实现深克隆时需要编写较为复杂的代码,而且当对象之间存在多重的嵌套引用时,为了实现深克隆,每一层对象对应的类都必须支持深克隆,实现起来可能会比较麻烦。

3.适用场景

在以下情况下可以考虑使用原型模式:

(1) 创建新对象成本较大(如初始化需要占用较长的时间,占用太多的CPU资源或网络资源),新的对象可以通过原型模式对已有对象进行复制来获得,如果是相似对象,则可以对其成员变量稍作修改。

(2) 如果系统要保存对象的状态,而对象的状态变化很小,或者对象本身占用内存较少时,可以使用原型模式配合备忘录模式来实现。

(3) 需要避免使用分层次的工厂类来创建分层次的对象,并且类的实例对象只有一个或很少的几个组合状态,通过复制原型对象得到新实例可能比使用构造函数创建一个新实例更加方便。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: