温故知新(3)——原型模式
概述
说起原型模式,一般理解就是“克隆”一个对象,因此经常被人忽视。前两天看了一篇博文还有博文后面的讨论(http://www.cnblogs.com/winter-cn/archive/2009/12/02/1614987.html),并参考了GOF的解释,对原型模式有了一点新的体会。
GOF关于原型模式的意图是这样描述的:
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
这句话有两个子句,前一个说新对象的类型与原型对象的一致(用原型实例指定);后一个说创建新对象的方法是拷贝(克隆操作)。我们常常忽略的是第一个子句,既原型模式如何确定新建对象的类型。用通俗的说法解释一下就是,“给我一个和你一样的对象”。这里面的你就暗含了类型的确定,我可能不知道你的名字、成份、政治面貌,只知道你可以完成一个定特任务,所以我这时只要要求你提供一个和你一样类型的对象就可以了(你提供的对象当然要和你是同样类型的,否则不能保证可以完成任务)。
以下是GOF书中提到的原型模式的优点:
1、运行时增加和删除产品;
2、改变值以指定新对象;
3、改变结构以指定新对象;
4、减少子类构造;
5、用动态配置应用。
个人的观点的总结:
1、原型模式对客户端隐藏了具体的类型名,对新对象的创建逻辑进行了封装,避免客户端反复出现构造对象的代码;
2、原型模式避免了建立工厂类,代码比较简单。
3、从使用原型克隆的方式可能会带来效率的提升(比如支持内存复制的语言,或者构造对象需要包含IO操作等情况)
4、原型模式可以以动态的原型对象为模板构建新对象,提供了更好的灵活性。(比如可以从原型A复制一些对象,然再修改A,以修改后的A为原型新建一些对象)。
原型模式要求每一个原型类都必须实现“克隆”操作,通常“克隆”是指“深克隆”,但对于一些无需改变的固定子对象,则可以实现为“浅克隆”,类似享元模式,共享这些对象。
结构
原型模式的类图:
原型模式的结构相对简单,只包含三种参与者:
1、克隆自身的原型接口——IPrototype;
2、原型接口的实现类——Prototype;
3、客户端代码——Client;
此外.NET Framework 中提供的ICloneable接口,并不能完整的表达原型模式(具体讨论,详见上面提到博文)。
示例
业务场景:假设我们有一个在线文档编辑系统,系统为提供了文档模板,用户可以从模板开始编写自己的文档,而不需要每次从头开始。
假设模板对象加载的消耗是比较大的,因此只加载一次模板,用户使用时可以从模板复制一个新的文档对象进行编辑(复制操作成本相对低廉),是一种合理的方式。此处采用原型模式进行实现。
1、定义原型接口IPrototype<T>。接口进行了泛型的类型约束。
1: using System;
2:
3: namespace DesignPatterns.Prototype
4: {
5: /// <summary>
6: /// 原型接口
7: /// </summary>
8: /// <typeparam name="T">根据原型对象创建的新对象,类型与原型对象相同</typeparam>
9: public interface IPrototype<T> where T : IPrototype<T>
10: {
11: T Clone();
12: }
13: }
14:
2、文档类Document,实现上文提到的原型接口。
1: using System;
2:
3: namespace DesignPatterns.Prototype
4: {
5: /// <summary>
6: /// 文档
7: /// </summary>
8: public class Document : IPrototype<Document>
9: {
10: /// <summary>
11: /// 标题
12: /// </summary>
13: public string Title { get; set; }
14:
15: /// <summary>
16: /// 正文
17: /// </summary>
18: public string Context { get; set; }
19:
20: /// <summary>
21: /// 表格
22: /// </summary>
23: public Table Table { get; set; }
24:
25: /// <summary>
26: /// 打印方法
27: /// </summary>
28: public void Print()
29: {
30: Console.WriteLine(this.Title);
31: Console.WriteLine(this.Context);
32: Console.WriteLine(this.Table);
33: }
34:
35: /// <summary>
36: /// 克隆方法,接口实现
37: /// </summary>
38: /// <returns>新的文档对象</returns>
39: public Document Clone()
40: {
41: Document doc = new Document() { Title = this.Title, Context = this.Context };
42: doc.Table = new Table() { Name = this.Table.Name, RowCount = this.Table.RowCount, ColumnCount = this.Table.ColumnCount };
43: return doc;
44: }
45: }
46: }
47:
3、Document类中包含一个复杂类型属性Table,Table类的实现如下:
1: using System;
2:
3: namespace DesignPatterns.Prototype
4: {
5: /// <summary>
6: /// 表格
7: /// </summary>
8: public class Table
9: {
10: /// <summary>
11: /// 表名
12: /// </summary>
13: public string Name { get; set; }
14:
15: /// <summary>
16: /// 行数
17: /// </summary>
18: public int RowCount { get; set; }
19:
20: /// <summary>
21: /// 列数
22: /// </summary>
23: public int ColumnCount { get; set; }
24:
25: public override string ToString()
26: {
27: return string.Format("{0}: {1}行,{2}列", this.Name, this.RowCount, this.ColumnCount);
28: }
29: }
30: }
31:
4、客户端代码。客户端代码中用LoadTemplate方法模拟了模板的加载行为。
1: using System;
2:
3: namespace DesignPatterns.Prototype
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: var template = LoadTemplate();
10: template.Print();
11: var copy = template.Clone();
12: copy.Print();
13:
14: copy.Title = "2012年8月业绩统计";
15: copy.Context = "环球贸易公司2012年8月业绩统计。。。";
16: copy.Table.RowCount = 61;
17: template.Print();
18: copy.Print();
19:
20: Console.WriteLine("按任意键结束...");
21: Console.ReadKey();
22: }
23:
24: /// <summary>
25: /// 模板文档的加载(假设存在IO等复杂操作)
26: /// </summary>
27: /// <returns></returns>
28: private static Document LoadTemplate()
29: {
30: Document doc = new Document() { Title = "X年X月业绩统计", Context = "XXX公司X年X月业绩统计。。。" };
31: doc.Table = new Table() { Name = "业绩统计表", RowCount = 45, ColumnCount = 7 };
32: return doc;
33: }
34: }
35: }
36:
5、运行,查看结果。