相信大多数的人都看过《西游记》,对孙悟空拔毛变出小猴子的故事情节应该都很熟悉。孙悟空可以用猴毛根据自己的形象复制出很多跟自己一模一样的小猴兵出来,其实在设计模式中也有一个类似的模式,我们可以通过一个原型对象来克隆出多个一模一样的对象,这个模式就是原型模式。

原型模式(Prototype) 学习难度:★★★☆☆ 使用频率:★★★☆☆

一、大同小异的工作周报

  M公司一直在使用自行开发的一个OA系统进行日常工作办理,但在使用过程中,越来越多的人对工作周报的创建和编写模块产生了抱怨。追其原因,M公司的OA管理员发现,由于某些岗位每周工作存在重复性,工作周报内容都大同小异,如下图所示:

  这些周报只有一些小地方存在差异,但是现行系统每周默认创建的周报都是空白报表,因此用户只能通过重新输入或不断地复制与粘贴来填写重复的周报内容,极大地降低了工作效率,浪费宝贵的时间。如何快速创建相同或者相似的工作周报,成为了M公司软件开发人员的一个新问题。

  M公司开发人员经过分析,决定按照以下思路对工作周报模块进行重新设计:

  (1)除了允许用户创建新周报外,还允许用户将创建好的周报保存为模板(也就是原型)。

  (2)用户在再次创建周报时,可以创建全新的周报,还可以选择合适的模板复制生成一个相同的周报,然后对新生成的周报根据实际情况进行修改,产生新的周报。

二、原型模式概述

2.1 关于原型模式

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

原型模式(Prototype):使用原型实例指定创建对象的种类,并且通过拷贝这些原 型创建新的对象。原型模式是一种对象创建型模式。

  需要注意的是,通过克隆方法所创建的对象时全新的对象。

  原型模式的结构如下图所示:

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

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

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

2.2 基本实现方法

  (1)通用实现方法

    public class ConcretePrototype : Prototype
{
// 克隆方法
public override Prototype Clone()
{
// 创建新对象
Prototype prototype = new ConcretePrototype();
prototype.CustomAttr = this.CustomAttr; return prototype;
}
}

  (2)借助C#语言的Clone方法

    public class ConcretePrototypeB : ICloneable
{
public int i = ;
public string customAttr = "hello prototype";
public ConcretePrototype a = new ConcretePrototype(); public object Clone()
{
// 实现深复制-方式1:依次赋值和实例化
ConcretePrototypeB newObj = new ConcretePrototypeB();
newObj.a = new ConcretePrototype();
newObj.a.CustomAttr = this.a.CustomAttr;
newObj.i = this.i; return newObj;
} public new object MemberwiseClone()
{
// 实现浅复制
return base.MemberwiseClone();
} public override string ToString()
{
string result = string.Format("I的值为{0},A为{1}", this.i.ToString(), this.a.CustomAttr);
return result;
}
}

三、基于原型模式的工作周报

3.1 设计思路

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

  这里,Object相当于抽象原型类,而所有实现了ICloneable接口的类都相当于具体原型类。

3.2 实现代码

  (1)WeeklyLog代码

    /// <summary>
/// 工作周报:具体原型类
/// 考虑到代码可读性和易理解性,只列出部分与原型模式相关的核心代码
/// </summary>
public class WeeklyLog : ICloneable
{
public string Name { get; set; }
public string Date { get; set; }
public string Content { get; set; } public object Clone()
{
WeeklyLog obj = new WeeklyLog();
obj.Name = this.Name;
obj.Date = this.Date;
obj.Content = this.Content; return obj;
}
}

  (2)Client代码

    public class Client
{
public static void PrintWeeklyLog(WeeklyLog log)
{
if (log == null)
{
return;
} Console.WriteLine("----------- start : M公司个人工作周报 -----------");
Console.WriteLine("周次:{0}", log.Date);
Console.WriteLine("员工:{0}", log.Name);
Console.WriteLine("内容:{0}", log.Content);
Console.WriteLine("----------- end : M公司个人工作周报 -----------");
} public static void V1()
{
// First version
WeeklyLog log = new WeeklyLog();
log.Name = "Victor";
log.Date = "第11周";
log.Content = "这周工作太忙,每天都在加班!~~~~(>_<)~~~~";
PrintWeeklyLog(log);
// Second version based on First version
WeeklyLog log2 = log.Clone() as WeeklyLog;
log2.Date = "第12周";
PrintWeeklyLog(log2);
// Third version based on First version
WeeklyLog log3 = log.Clone() as WeeklyLog;
log3.Date = "第13周";
PrintWeeklyLog(log3);
}
}

  执行结果如下图所示:

  

3.3 带附件的周报

  经过改进后的工作周报已经获得用户的一致好评,但是,又有员工提出有些周报带有附件,如果使用上面的实现,周报的附件并不能够复制成功。在进入设计之前,我们先来了解一下浅复制和深复制。

  (1)浅复制:复制一个对象的时候,仅仅复制原始对象中所有的非静态类型成员和所有的引用类型成员的引用。(新对象和原对象将共享所有引用类型成员的实际对象)

  (2)深复制:复制一个对象的时候,不仅复制所有非静态类型成员,还要复制所有引用类型成员的实际对象

          

  先来看看浅复制的实现:

  

    public class WeeklyLog : ICloneable
{
public string Name { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public IList<Attachment> attachmentList { get; set; } // v2
public WeeklyLog()
{
this.attachmentList = new List<Attachment>();
} public object Clone()
{
// v1
WeeklyLog obj = new WeeklyLog();
obj.Name = this.Name;
obj.Date = this.Date;
obj.Content = this.Content;
// v2 -- shallow copy
obj.attachmentList = this.attachmentList;
return obj;
}
}

  客户端测试代码:

        public static void Main()
{
// First version
WeeklyLog log = new WeeklyLog();
log.attachmentList.Add(new Attachment() { Name = "工作总结20170426-20170501_Victor.xlsx" });
// Second version
WeeklyLog log2 = log.Clone() as WeeklyLog;
// Compare 2 object
Console.WriteLine("周报是否相同:{0}", object.ReferenceEquals(log, log2));
// Compare 2 attachment
Console.WriteLine("附件是否相同:{0}", object.ReferenceEquals(log.attachmentList[], log2.attachmentList[]));
}

  由于使用的是浅复制,因此附件对象的内存地址指向的是同一个对象。

  

  再来看看深复制的实现:

    [Serializable]
public class WeeklyLog : ICloneable
{
public string Name { get; set; }
public string Date { get; set; }
public string Content { get; set; }
public IList<Attachment> attachmentList { get; set; } // v2,v3
public WeeklyLog()
{
this.attachmentList = new List<Attachment>();
} public object Clone()
{
// v1
//WeeklyLog obj = new WeeklyLog();
//obj.Name = this.Name;
//obj.Date = this.Date;
//obj.Content = this.Content;
// v2 -- shallow copy
//obj.attachmentList = this.attachmentList;
//return obj;
// v3 -- deep copy
BinaryFormatter bf = new BinaryFormatter();
MemoryStream ms = new MemoryStream();
bf.Serialize(ms, this);
ms.Position = ;
return bf.Deserialize(ms);
}
}

  这里借助序列化来实现深复制,因此别忘记给需要深复制的对象的类定义上面加上可序列化的标签[Serializable]。

  客户端测试代码:

    public static void Main()
{
// First version
WeeklyLog log = new WeeklyLog();
log.attachmentList.Add(new Attachment() { Name = "工作总结20170426-20170501_Victor.xlsx" });
// Second version
WeeklyLog log2 = log.Clone() as WeeklyLog;
// Compare 2 object
Console.WriteLine("周报是否相同:{0}", object.ReferenceEquals(log, log2));
// Compare 2 attachment
Console.WriteLine("附件是否相同:{0}", object.ReferenceEquals(log.attachmentList[], log2.attachmentList[]));
}

  此时,借助深复制克隆的对象已经不再是指向同一个内存地址的了,因此两个附件也是不同的:

  

四、原型模式深入之原型管理器

4.1 何为原型管理器

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

  原型管理器对应的结构图如下:

4.2 公文管理器的设计与实现

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

  开发人员决定使用原型管理器来设计,其结构图如下:

  (1)抽象原型与具体原型

    public interface OfficeDocument : ICloneable
{
new OfficeDocument Clone(); // 隐藏ICloneable的Clone接口方法定义
void Display();
} public class FAR : OfficeDocument
{
public OfficeDocument Clone()
{
return new FAR();
} public void Display()
{
Console.WriteLine("<<可行性分析报告>>");
} object ICloneable.Clone()
{
return this.Clone();
}
} public class SRS : OfficeDocument
{
public OfficeDocument Clone()
{
return new SRS();
} public void Display()
{
Console.WriteLine("<<软件需求规格说明书>>");
} object ICloneable.Clone()
{
return this.Clone();
}
}

  (2)原型管理器

    public class PrototypeManager
{
private Dictionary<string, OfficeDocument> dictOD = new Dictionary<string, OfficeDocument>(); public static PrototypeManager GetInstance()
{
return Nested.instance;
} class Nested
{
static Nested() { }
internal static readonly PrototypeManager instance = new PrototypeManager();
} private PrototypeManager()
{
dictOD.Add("FAR", new FAR());
dictOD.Add("SRS", new SRS());
} public void AddOfficeDocument(string key, OfficeDocument doc)
{
dictOD.Add(key, doc);
} public OfficeDocument GetOfficeDocumentByKey(string key)
{
key = key.ToUpper();
if (!dictOD.ContainsKey(key))
{
return null;
} return dictOD[key].Clone();
}
}

  这里PrototypeManager采用了单例模式(有利于节省系统资源),并通过一个Dictionary集合保存原型对象,客户端便可以通过Key来获取对应原型的克隆对象。

  (3)客户端代码

    public static void Main()
{
PrototypeManager pm = PrototypeManager.GetInstance(); OfficeDocument doc1, doc2, doc3, doc4;
doc1 = pm.GetOfficeDocumentByKey("FAR");
doc1.Display();
doc2 = pm.GetOfficeDocumentByKey("FAR");
doc2.Display(); Console.WriteLine("是否是同一个FAR:{0}", object.ReferenceEquals(doc1, doc2)); doc3 = pm.GetOfficeDocumentByKey("SRS");
doc3.Display();
doc4 = pm.GetOfficeDocumentByKey("SRS");
doc4.Display(); Console.WriteLine("是否是同一个SRS:{0}", object.ReferenceEquals(doc3, doc4));
}

  运行结果如下:

  

五、原型模式总结

5.1 主要优点

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

  (2)可以使用深复制的方式保存对象的状态。将对象复制一份并将其状态保存起来,以便于在使用的时候使用,比如恢复到某一个历史状态,可以辅助实现撤销操作。

5.2 主要缺点

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

  (2)为了支持深复制,当对象之间存在多重嵌套引用关系时,每一层对象都必须支持深复制,实现起来可能比较麻烦。

5.3 应用场景

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

参考资料

  刘伟,《设计模式的艺术—软件开发人员内功修炼之道》

作者:周旭龙

出处:http://edisonchou.cnblogs.com

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。

设计模式的征途—5.原型(Prototype)模式的更多相关文章

  1. 设计模式C++描述----08.原型(Prototype)模式

    一. 概述 定义:用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 换句话说,就是不用重新初始化对象,而是动态地获得对象运行时的状态. 再说明白点,就是要一个拷贝过构造函数类似功能的接 ...

  2. Java 实现原型(Prototype)模式

    public class BaseSpoon implements Cloneable {//spoon 匙, 调羹 String name; public String getName() { re ...

  3. 原型(Prototype)模式

    原型模式属于对象的创建模式.通过给出一个原型对象来指明所有创建的对象的类型,然后用复制这个原型对象的办法创建出更多同类型的对象.这就是原型模式的用意.原型模式的结构 原型模式要求对象实现一个可以“克隆 ...

  4. 《图解设计模式》读书笔记3-2 Prototype模式

    目录 Prototype(原型)模式的由来 类图 代码 角色 我的理解 Prototype(原型)模式的由来 创建一个实例,可以关键字new创建.但有时候,我们需要在不指定类名的前提下生成实例,比如: ...

  5. 设计模式--原型(Prototype)模式

    写这些也许有人认为“为了模式而模式”.Insus.NET所想到的,每个大师成为大师之前,也许都得这样做. 走路,从小就开始学,直至现在,谁还不是为了走路而走路?一直重复着...... 很多人没有分享自 ...

  6. 六、原型(Prototype)模式

    原型模式是对象的创建模式,通过给出一个原型对象来指明所要创建的对象的类型.然后用复制这个原型对象的方法来创建出更多同类型的对象. 原型模式可以不用重新初始化对象,而动态的获取对象运行时的状态.使用原型 ...

  7. 克隆复制可使用原型( Prototype)设计模式

    今天有学习设计模式的原型(Prototype)<设计模式--原型(Prototype)模式>http://www.cnblogs.com/insus/p/4152773.html .为了加 ...

  8. 设计模式的征途(C#实现)—文章目录索引

    1.预备篇 UML类图10分钟快速入门 2.创建型模式 ① 设计模式的征途-01.单例(Singleton)模式 ② 设计模式的征途-02.简单工厂(Simple Factory)模式 ③ 设计模式的 ...

  9. 小菜学习设计模式(四)—原型(Prototype)模式

    前言 设计模式目录: 小菜学习设计模式(一)—模板方法(Template)模式 小菜学习设计模式(二)—单例(Singleton)模式 小菜学习设计模式(三)—工厂方法(Factory Method) ...

随机推荐

  1. fis-plus 学习笔记

    学习了一些fls-plus前端集成的东西:学的很皮毛,很多都是对官网的解释希望与大家分享,并能得到大家的指正. 参考文档:http://oak.baidu.com/fis-plus/document. ...

  2. 看了一个烟花的html作品 --引用:http://www.w3cfuns.com/blog-5444049-5404365.html

    最近老大想把项目改成响应式,一直在学习没时间更新博客.今天看到一个原生的js烟花项目,感觉很好,把记下来,以后把妹用. [run]<!DOCTYPE html><html>&l ...

  3. ICC_lab总结——ICC_lab3:布局

    PS:字丑,禁止转载. 做到了ICC workshop的第三个实验,理论与实践相结合,于是,先放一张总结图,这张总结图来自前面的博客,放在这里用来体现理论和实践的联系: 这个就是布局的理论部分,在IC ...

  4. 手动编写JQUERY插件

    就拿一个简单的示例来说,鼠标点击输入框,提示文字消息,鼠标移开,再显示提示文字. <script type="text/javascript"> //编写插件 (fun ...

  5. 从spring官网下载spring 架包

    1.找到spring官网地址:http://spring.io/ 2.点击projects 3.点击springframework 4.点击图片

  6. 我所理解的javascript中函数的作用域和作用域链

    本文为原创,转载请注明出处: cnzt       文章:cnzt-p 写在前面 一周木有更新了,今天终于攻克了自行车难关,非常开心,特意来一更~ (那些捂嘴偷笑的人我看到你们了快把嘴闭上我会假装没看 ...

  7. Java面试指导

    Java面试指导   想要成为合格的Java程序员或工程师到底需要具备哪些专业技能,面试者在面试之前到底需要准备哪些东西呢?本文陈列的这些内容既可以作为个人简历中的内容,也可以作为面试的时候跟面试官聊 ...

  8. 老李推荐:第6章5节《MonkeyRunner源码剖析》Monkey原理分析-事件源-事件源概览-事件

    老李推荐:第6章5节<MonkeyRunner源码剖析>Monkey原理分析-事件源-事件源概览-事件   从网络过来的命令字串需要解析翻译出来,有些命令会在翻译好后直接执行然后返回,但有 ...

  9. google官方的下拉刷新+自定义上拉加载更多

    转载请标注转载:http://blog.csdn.net/oqihaogongyuan/article/details/50949118 google官方的下拉刷新+自定义上拉加载更多 现在很多app ...

  10. C#, VB.NET如何加密PDF文档

    在日常工作中,人们通常通过加密PDF文档的方式来保护PDF文档.不管是公司还是个人,使用PDF加密术来设置一些权限是必不可少的.为了使PDF文档既可读又不能被未授权的用户所更改,一份PDF文档往往需要 ...