艾伟_转载:C# Design Patterns (5) - Prototype

简介: 本帖介绍 Prototype Pattern (原型模式),并以一个「人事招聘程序」作为示例来说明。--------------------------------------------------------本帖的示例下载点:http://files.cnblogs.com/WizardWu/090713.zip第一个示例为 Console Mode (控制台应用程序) 项目,第二个示例为 ASP.NET 网站项目。

本帖介绍 Prototype Pattern (原型模式),并以一个「人事招聘程序」作为示例来说明。

--------------------------------------------------------
本帖的示例下载点:
http://files.cnblogs.com/WizardWu/090713.zip
第一个示例为 Console Mode (控制台应用程序) 项目,第二个示例为 ASP.NET 网站项目。
执行示例需要 Visual Studio 2008 或 IIS + .NET 3.0,不需要数据库。
--------------------------------------------------------

Prototype Pattern (原型模式)

Specify the kinds of objects to create using a prototypical instance, and create new objects by copying this prototype.
                                 - Design Patterns: Elements of Reusable Object-Oriented Software

 

原型模式就是以一个既有的原型实例当作范本,利用复制的方式,动态获得这个原型实例的状态以及全部的字段和属性,以此创建一或多个相同的对象,而且不需要知道任何创建的细节。

Prototype 模式打个通俗的比方:假如您在图书馆看到几本自己喜欢的书籍,当看到某些知识点时,想在上面作相关记号,但由于其是图书馆的书,不能在上面乱涂乱画。此时您只好把相关的章节,用复印机把它复印出来,然后在自己复印的纸张上作记号。在 Prototype Pattern 里,Clone 方法就如同此种复印的动作,用户从一个既有的原型实例 (如同图书馆里的书),复印后得到一或多个新的拷贝,不会破坏原本的原型,且用户不必知道原型的内容和格式。

Prototype 也具有一种「展示」的意味,就像是车展上的「原型」车款。当您对某个车款感兴趣时,您可购买相同的车款,而不是车展上展示的那辆车。

在软件设计方面,也常需要进行此种对象复制。例如我们要写一套室内设计软件,软件的操作界面上有一条 Toolbar,用户只要单击 Toolbar 上的 Button,或用鼠标拖曳到设计窗格中,就可创建一个桌子或椅子的副本,并可事后改变它的颜色或位置。当设计师改变设计图中的副本对象时,Toolbar 上的「原型」对象并不会跟着被改变。同样的观念,亦适用于工业设计 CAD 软件、图像处理软件,以及 Visual Studio 等各种软件的设计。


Prototype 模式的重点在于 Clone 方法,它负责复制 (克隆) 出一个新的对象并返回,而不是用 new 运算符和某个类的构造函数去创建实例。在此模式中,派生类如何覆写父类的 Clone 方法将是重点。而 clone 的方式,又可分为「浅拷贝 (shallow copy)」和「深拷贝 (deep copy)」,在介绍这个 Prototype 模式之前,先简单介绍一下这两种拷贝方式的差异 [1], [2], [3], [4] :

  • 浅拷贝; 浅表复制 (shallow copy):对象拷贝时,如果字段是「值类型 (Value Type)」,则直接复制其值 (亦即复制整个字段);若字段为「引用类型 (Reference Type)」,则只复制其「引用 (reference; pointer)」,但不复制引用的字段,亦即若更改了任一个副本对象的某一个「引用类型」字段,则原型正本对象、其他副本对象,也全部会一并更改 (如同本帖的第三个示例 02_Employee / 02_ShallowCopy_fail.aspx.cs),也就是说正本和所有的副本,都指向了内存的同一个位置。
  • 深拷贝; 深层复制 (deep copy):不论对象的字段为「值类型」或「引用类型」,都会完整地复制,而且这些字段和属性都是完全独立的。在深拷贝中,所有的对象都是重复的。

另补充,.NET 的类型系统,分为「值类型」、「引用类型」两种,其对象在内存中的存储方式不同,如下:

  • 值类型:只需要一段单独的内存,用于存储实际的数据在「栈 (Stack)」里,例如:int、byte、float、double、bool、struct、enum、char、...等类型。
  • 引用类型:需要两段内存,第一段存储实际的数据,其总是位于「堆 (Heap)」中;第二段是一个存在「栈」里的引用 (reference; pointer),其指向数据在「堆」中的实际存放位置,例如:object、string、class (包括自定义类)、interface、delegate、array (参考本帖的第三、第四个示例) 等类型。

但 string (字符串) 较特殊。string 虽然是「引用类型」,但却拥有「值类型」的特性。在 Prototype Pattern 及本帖的四个示例中,当透过 MemberwiseClone 方法做「浅拷贝」时,对象的 string 字段仍会被完整地复制,其结果就如同 int 等「值类型」的字段一样。


如下图 1 及下方示例 01_Shell,我们可透过自定义的 Prototype 抽象类,搭配 .NET 最顶层基类 System.Object 的 MemberwiseClone 方法,达成对象的「浅拷贝」,亦即复制某个对象其所有「字段 (field)」的值;但在 .NET 中,亦可舍弃此一自定义抽象类,让图 1 中的 ConcretePrototype1 类、ConcretePrototype2 类,改为实现 .NET 原生的 System.ICloneable 接口,透过实现此接口唯一的一个 Clone 方法,来达成对象的「浅拷贝」或「深拷贝」。


图 1 此图为 Prototype 模式的经典类图

 

01_Shell / Program.cs
using System;

namespace _01_Shell
{
    
//客户端程序
    class Program
    {
        
static void Main(string[] args)
        {
            ConcretePrototype1 p1 
= new ConcretePrototype1("I");          //原型对象(来自外部的第一个实例)
            ConcretePrototype1 c1 = (ConcretePrototype1)p1.Clone();        //浅拷贝(shallow copy)
            Console.WriteLine("Cloned: {0}", c1.Id);

            ConcretePrototype2 p2 
= new ConcretePrototype2("II");         //原型对象(来自外部的第一个实例)
            ConcretePrototype2 c2 = (ConcretePrototype2)p2.Clone();        //浅拷贝(shallow copy)
            Console.WriteLine("Cloned: {0}", c2.Id);
                        
            Console.Read();
        }
    }

    
//每个具体的「原型」类,要实现的抽象类或接口。
    
//亦可舍弃此一自定义抽象类或接口,让 ConcretePrototype 类改为实现 .NET 原生的 System.ICloneable 接口
    abstract class Prototype
    {
        
private string id;                            //这个字段在派生类的 Clone 方法被调用时,会自动被拷贝
                
        
public Prototype(string id)                       //构造函数
        {
            
this.id = id;
        }

        
public string Id
        {
            
get { return id; }
        }

        
//返回 Prototype 类型。
        public abstract Prototype Clone();
    }

    
class ConcretePrototype1 : Prototype
    {
        
public ConcretePrototype1(string id)         //构造函数
            : base(id)
        {
        }

        
//返回 Prototype 类型。在客户端程序中,依赖和获得的是 Prototype 抽象类,并以其定义来操作其派生类
        public override Prototype Clone()
        {
            
return (Prototype)this.MemberwiseClone();          //浅拷贝(shallow copy)
        }
    }


    
class ConcretePrototype2 : Prototype
    {
        
public ConcretePrototype2(string id)        //构造函数
            : base(id)
        {
        }

        
//返回 Prototype 类型。在客户端程序中,依赖和获得的是 Prototype 抽象类,并以其定义来操作其派生类
        public override Prototype Clone()
        {
            
return (Prototype)this.MemberwiseClone();       //浅拷贝(shallow copy)
        }
    }
}

/*
执行结果:

Cloned: I
Cloned: II

*/

 

上方图 1 的 Class Diagram,以及「Shell (壳)」示例中,客户端程序透过抽象类 Prototype 的定义来操作其派生类。先选择一个「原型」实例,亦即 ConcretePrototype1 类或 ConcretePrototype2 类的实例,通过调用它所覆写抽象父类的 Clone 方法,获得一个和它一样、有相同 id 值的新对象,而非透过 new 运算符去创建实例。而拷贝完成后,拷贝的原型样本 (p1、p2),和副本 (c1、c2) 是两个独立的对象,可以独立变化和修改。


但为什么不用 new 运算符加上某个类的构造函数去创建实例,而要再衍生出这种 Prototype Pattern 呢?其中一个原因,是系统设计上,类的种类可能会很多,而难以整合成特定的自定义类时;或类与类之间有大量平行阶层结构,类的数量过多会造成管理上的困难。例如本帖一开始提到的室内设计软件,桌子类和椅子类,又可各分成: 方形的、圆形的、其他各种形状的…,若全部都要写成不同的类,类的数量会很可观。

此外,也是为了避免整个系统中,类与类之间结构的剧烈变化,避免为了重载一个新的函数,导致我们要修改的不是一个类,而是整个继承关系体系里的每一个类。

另一个原因,是用户在用鼠标操作这个室内设计软件时 (运行时期),若要重复创建相同的圆形桌子对象时,用 Prototype Pattern 这种对象复制的方式,由于对象初始化的内容都相同,会比从头用类去创建新的实例要容易,同时能在客户端程序中隐藏对象创建的细节,且在速度和性能上会较优 [15], [17]。


接下来的三个示例,为一个人事招聘系统的部分代码,我们以此为例来实现原型模式。某间公司要招聘「程序员」、「行政文员」两种职务,其中的 Employee 为顶层的抽象类,两个派生类 Developer 和 Typist 必须实现其 Clone 方法。示例执行结果如下图 3。

由于两个派生类 Developer 和 Typist,其成员都是 int 等「值类型」或 string,因此我们在客户端程序 (Page_Load) 中执行「浅拷贝」时,既有的第一个「原型」对象 - dev 和 typist 实例,其所有的字段,会被逐位复制到新对象 devCopy1、devCopy2、typistCopy1 中,且正本对象和副本对象的字段各自独立、存储在内存的不同位置。

此外,我们看到 Developer 第一个「拷贝」出来的对象 devCopy1,它的 Role、PreferredLanguage 字段,都和原型对象 dev 一样,是「资深工程师」和「C#」,而我们可以将 Name 字段改成「李大同」,而不影响原型对象 dev 的 Name 字段。


图 2 Sybase PowerDesigner 12.5 的「反向工程」功能,已可解析 C# 3.0 的 Auto-Implemented Property 语法

 

02_Employee / 01_ShallowCopy.aspx.cs
using System;
using com.cnblogs.WizardWu.Sample01;

//客户端程序
public partial class _01_ShallowCopy : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Developer dev 
= new Developer();          //原型对象(来自外部的第一个实例)
        dev.Name = "王小明";
        dev.Role 
= "资深工程师";
        dev.PreferredLanguage 
= "C#";

        Developer devCopy1 
= (Developer)dev.Clone();      //浅拷贝(shallow copy)
        devCopy1.Name = "李大同";

        Developer devCopy2 
= (Developer)dev.Clone();      //浅拷贝(shallow copy)
        devCopy2.Name = "吴宇泽";
        devCopy2.Role 
= "研发工程师";
        devCopy2.PreferredLanguage 
= "C++";

        Response.Write(dev 
+ "
");
        Response.Write(devCopy1 
+ "
");
        Response.Write(devCopy2 
+ "

 

" );

        
/*  执行结果:

        王小明 - 资深工程师 - C#
        李大同 - 资深工程师 - C#
        吴宇泽 - 研发工程师 - C++

        
*/

        Typist typist 
=   new  Typist();                     // 原型对象(来自外部的第一个实例)
        typist.Name  =   " 左婉青 " ;
        typist.Role 
=   " 行政文员 " ;
        typist.WordsPerMinute 
=   120 ;

        Typist typistCopy1 
=  (Typist)typist.Clone();             // 浅拷贝(shallow copy)
        typistCopy1.Name  =   " 周玉婷 " ;
        typistCopy1.WordsPerMinute 
=   115 ;

        Response.Write(typist 
+   "
" );
        Response.Write(typistCopy1 
+   "
" );

        
/*  执行结果:

        左婉青 - 行政文员 - 120 字/分
        周玉婷 - 行政文员 - 115 字/分

        
*/
    }
}

// 服务器端程序
namespace  com.cnblogs.WizardWu.Sample01
{
    
// 员工 抽象类。
    
// 每个具体的「原型」类,要实现的抽象类或接口
     abstract   class  Employee
    {
        
// 返回 Employee 类型。
         public   abstract  Employee Clone();

        
// .NET 3.0 的 Auto-Implemented Property (Automatic Properties) 语法,会自动产生对应的同名 private field
         public   string  Name {  get set ; }        // 姓名
         public   string  Role {  get set ; }          // 职务
    }

    
// 程序员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Developer : Employee
    {
        
public   string  PreferredLanguage {  get set ; }

        
// 返回 Employee 类型。在客户端程序中,依赖和获得的是 Prototype 抽象类,并以其定义来操作其派生类
         public   override  Employee Clone()
        {
            
return  (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Developer();                            // 深拷贝(deep copy)
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} " , Name, Role, PreferredLanguage);
        }
    }

    
// 文员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Typist : Employee
    {
        
public   int  WordsPerMinute {  get set ; }

        
// 返回 Employee 类型。在客户端程序中,依赖和获得的是 Prototype 抽象类,并以其定义来操作其派生类
         public   override  Employee Clone()
        {
            
return  (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Typist();                                  // 深拷贝(deep copy)
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} 字/分 " , Name, Role, WordsPerMinute);
        }
    }    

// end of namespace

 

 
图 3 上方示例 01_ShallowCopy.aspx.cs 的执行结果。在两个派生类的 Clone 方法中,用 MemberwiseClone 方法实现了「浅拷贝」


接下来我们要把上方的示例,在派生类 Developer 中,添加一个「引用类型」的数组 int[] intArray。由于数组是「引用数型」,因此在做「浅拷贝」时,原始对象及其复本引用的是同一个对象 (指向内存的同一个位置);当我们在 Page_Load 做完 Developer 的两次「浅拷贝」,再把最后一位程序员「吴宇泽」,他的 intArray 数组的第一个元素,其值从 1 改为 9,此时执行结果如下图 4 ,您会发现其他两位程序员 - 王小明、李大同,他们的 intArray 数组的第一个元素,也都同时被改成 9 了。

 

02_Employee / 02_ShallowCopy_fail.aspx.cs
using System;
using com.cnblogs.WizardWu.Sample02;

//客户端程序
public partial class _02_ShallowCopy_fail : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Developer dev 
= new Developer();          //原型对象(来自外部的第一个实例)
        dev.Name = "王小明";
        dev.Role 
= "资深工程师";
        dev.PreferredLanguage 
= "C#";

        Developer devCopy1 
= (Developer)dev.Clone();      //浅拷贝(shallow copy)
        devCopy1.Name = "李大同";

        Developer devCopy2 
= (Developer)dev.Clone();      //浅拷贝(shallow copy)
        devCopy2.Name = "吴宇泽";
        devCopy2.Role 
= "研发工程师";
        devCopy2.PreferredLanguage 
= "C++";

        Response.Write(dev 
+ "
");
        Response.Write(devCopy1 
+ "
");
        Response.Write(devCopy2 
+ "

 

" );

        Response.Write(dev.Display() 
+   "
" );
        Response.Write(devCopy1.Display() 
+   "
" );
        Response.Write(devCopy2.Display() 
+   "

 

" );

        
// 只将最后一个程序员「吴宇泽」,他的数组(引用类型) 的第一个元素,其值从 1 改为 9
        devCopy2.intArray[ 0 =   9 ;

        Response.Write(dev.Display() 
+   "
" );
        Response.Write(devCopy1.Display() 
+   "
" );
        Response.Write(devCopy2.Display() 
+   "

 

" );

        
/*  执行结果:

        王小明 - 资深工程师 - C#
        李大同 - 资深工程师 - C#
        吴宇泽 - 研发工程师 - C++
         
        1, 2, 3, 
        1, 2, 3, 
        1, 2, 3, 

        9, 2, 3, 
        9, 2, 3, 
        9, 2, 3, 

        
*/

        Typist typist 
=   new  Typist();                     // 原型对象(来自外部的第一个实例)
        typist.Name  =   " 左婉青 " ;
        typist.Role 
=   " 行政文员 " ;
        typist.WordsPerMinute 
=   120 ;

        Typist typistCopy1 
=  (Typist)typist.Clone();             // 浅拷贝(shallow copy)
        typistCopy1.Name  =   " 周玉婷 " ;
        typistCopy1.WordsPerMinute 
=   115 ;

        Response.Write(typist 
+   "
" );
        Response.Write(typistCopy1 
+   "
" );

        
/*  执行结果:

        左婉青 - 行政文员 - 120 字/分
        周玉婷 - 行政文员 - 115 字/分

        
*/
    }
}

// 服务器端程序
namespace  com.cnblogs.WizardWu.Sample02
{
    
// 员工 抽象类。
    
// 每个具体的「原型」类,要实现的抽象类或接口
     abstract   class  Employee
    {
        
// 返回 Employee 类型。
         public   abstract  Employee Clone();

        
// .NET 3.0 的 Auto-Implemented Property (Automatic Properties) 语法,会自动产生对应的同名 private field
         public   string  Name {  get set ; }
        
public   string  Role {  get set ; }
    }

    
// 程序员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Developer : Employee
    {
        
public   string  PreferredLanguage {  get set ; }

        
// 多加一个「引用类型(Reference Type)」成员 - 数组(Array)
         public   int [] intArray  =  {  1 2 3  };
        
public   string  Display()
        {
            
string  strReturn  =   string .Empty;
            
foreach  ( int  i  in  intArray)
            {
                strReturn 
+=  i.ToString()  +   " " ;
            }
            
return  strReturn;
        }

        
public   override  Employee Clone()
        {
            
return  (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Developer();                            // 深拷贝(deep copy)
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} " , Name, Role, PreferredLanguage);
        }
    }

    
// 文员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Typist : Employee
    {
        
public   int  WordsPerMinute {  get set ; }

        
public   override  Employee Clone()
        {
            
return  (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Typist();                                  // 深拷贝(deep copy)
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} 字/分 " , Name, Role, WordsPerMinute);
        }
    }

// end of namespace

 

 
图 4 Developer 类添加了一个「引用类型」的数组,造成「浅拷贝」时,只复制了「引用」,却未复制引用的字段


在下方的最后一个示例中,我们要更正前一个示例 02_ShallowCopy_fail.aspx.cs 的错误。我们把前述 Developer 类里面的 Clone 方法,从「浅拷贝」改成「深拷贝」,以搭配数组这个「引用类型」的拷贝;而另一个 Typist 类,由于没有「引用类型」成员,因此不需要更改,Clone 方法仍然延用「浅拷贝」。执行结果如下图 5,我们同样在 Page_Load 做 Developer 的两次拷贝,只不过这两次是「深拷贝」。结果符合我们需求,只有最后一位程序员「吴宇泽」,他的数组的第一个元素,其值从 1 改为 9,其他两位程序员则不受影响。

 

02_Employee / 03_DeepCopy.aspx.cs
using System;
using com.cnblogs.WizardWu.Sample03;

//客户端程序
public partial class _03_DeepCopy : System.Web.UI.Page
{
    
protected void Page_Load(object sender, EventArgs e)
    {
        Developer dev 
= new Developer();          //原型对象(来自外部的第一个实例)
        dev.Name = "王小明";
        dev.Role 
= "资深工程师";
        dev.PreferredLanguage 
= "C#";

        Developer devCopy1 
= (Developer)dev.Clone();      //深拷贝(deep copy)
        devCopy1.Name = "李大同";

        Developer devCopy2 
= (Developer)dev.Clone();      //深拷贝(deep copy)
        devCopy2.Name = "吴宇泽";
        devCopy2.Role 
= "研发工程师";
        devCopy2.PreferredLanguage 
= "C++";

        Response.Write(dev 
+ "
");
        Response.Write(devCopy1 
+ "
");
        Response.Write(devCopy2 
+ "

 

" );

        Response.Write(dev.Display() 
+   "
" );
        Response.Write(devCopy1.Display() 
+   "
" );
        Response.Write(devCopy2.Display() 
+   "

 

" );

        
// 只将最后一个程序员「吴宇泽」,他的数组(引用类型) 的第一个元素,其值从 1 改为 9
        devCopy2.intArray[ 0 =   9 ;

        Response.Write(dev.Display() 
+   "
" );
        Response.Write(devCopy1.Display() 
+   "
" );
        Response.Write(devCopy2.Display() 
+   "

 

" );

        
/*  执行结果:

        王小明 - 资深工程师 - C#
        李大同 - 资深工程师 - C#
        吴宇泽 - 研发工程师 - C++
         
        1, 2, 3, 
        1, 2, 3, 
        1, 2, 3, 

        1, 2, 3, 
        1, 2, 3, 
        9, 2, 3, 

        
*/

        Typist typist 
=   new  Typist();                     // 原型对象(来自外部的第一个实例)
        typist.Name  =   " 左婉青 " ;
        typist.Role 
=   " 行政文员 " ;
        typist.WordsPerMinute 
=   120 ;

        Typist typistCopy1 
=  (Typist)typist.Clone();             // 浅拷贝(shallow copy)
        typistCopy1.Name  =   " 周玉婷 " ;
        typistCopy1.WordsPerMinute 
=   115 ;

        Response.Write(typist 
+   "
" );
        Response.Write(typistCopy1 
+   "
" );

        
/*  执行结果:

        左婉青 - 行政文员 - 120 字/分
        周玉婷 - 行政文员 - 115 字/分

        
*/
    }
}

// 服务器端程序
namespace  com.cnblogs.WizardWu.Sample03
{
    
// 员工 抽象类。
    
// 每个具体的「原型」类,要实现的抽象类或接口
     abstract   class  Employee
    {
        
// 返回 Employee 类型。
         public   abstract  Employee Clone();

        
// .NET 3.0 的 Auto-Implemented Property (Automatic Properties) 语法,会自动产生对应的同名 private field
         public   string  Name {  get set ; }
        
public   string  Role {  get set ; }
    }

    
// 程序员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Developer : Employee
    {
        
public   string  PreferredLanguage {  get set ; }

        
// 多加一个「引用类型(Reference Type)」成员 - 数组(Array)
         public   int [] intArray  =  {  1 2 3  };
        
public   string  Display()
        {
            
string  strReturn  =   string .Empty;
            
foreach  ( int  i  in  intArray)
            {
                strReturn 
+=  i.ToString()  +   " " ;
            }
            
return  strReturn;
        }

        
public   override  Employee Clone()
        {
            
// return (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Developer();                            // 深拷贝(deep copy) - 做法一

            
// 深拷贝(deep copy) - 做法二
            Developer dev1  =   new  Developer();             // 用 new 创建此类的实例
            dev1.Name  =   this .Name;
            dev1.Role 
=   this .Role;
            dev1.PreferredLanguage 
=   this .PreferredLanguage;

            
return  dev1;
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} " , Name, Role, PreferredLanguage);
        }
    }

    
// 文员
    
// 继承 Employee 抽象类,或实现 .NET 的 ICloneable 接口,以便重写其 Clone 方法,创建作为当前实例副本的新对象
     class  Typist : Employee
    {
        
public   int  WordsPerMinute {  get set ; }

        
public   override  Employee Clone()
        {
            
return  (Employee)MemberwiseClone();         // 浅拷贝(shallow copy)。转型亦可用 as Employee
            
// return new Typist();                                  // 深拷贝(deep copy)
        }

        
public   override   string  ToString()
        {
            
return   string .Format( " {0} - {1} - {2} 字/分 " , Name, Role, WordsPerMinute);
        }
    }

// end of namespace

 

 
图 5 将 Developer 类中 Clone 方法里的「浅拷贝」改成「深拷贝」,以配合该类中「引用类型」成员的复制


--------------------------------------------------------

Prototype Pattern 适用的情景:

  • 当系统中,某个系列的类,变化和扩展特别频繁的时侯。
  • 当系统应该独立于它的产品创建时。
  • 想对客户端程序,隐藏类的具体内容。
  • 希望依用户的操作,在「执行时期」动态地加载 (dynamic loading) 或动态获得对象的状态。例如本帖一开始提到的室内设计软件,会依使用者鼠标的操作来动态创建副本。
  • 同上一点,当需要的类型不是编译时就能确定的,而是能在运行过程中动态选择的。
  • 当类型本身可枚举的种类非常固定时,例如一家软件公司,只有「主管、程序员、业务员」三种职务,当公司标到大型项目,需要招幕一百个程序员,与其通过某种机制 new 一百个程序员实例,不如通过一个现成的「程序员」原型实例,克隆一百个对象出来。
  • 当一个类的多个实例,他们之间的字段和属性只有些许不同时。
  • 当一个类的实例,只能有几种不同状态组合的其中一种时。
  • 当对象的初始化需要高成本,例如:构造函数的参数、字段数量很多很复杂时。

Prototype Pattern 的优点:

  • 可达到资源优化,避免用 new 创建实例会较消耗资源,且这样做速度也比 clone 对象慢 [15], [17], [18]。
  • 独立性和灵活性高,容易动态加载新功能。
  • 减少类的数量。避免类的种类太多时,其子类数量会迅速地增加。
  • 能在客户端程序中隐藏对象创建的细节。

Prototype Pattern 的缺点:

  • 每一个类都需要配备并覆写 Clone 方法,且撰写时需要做整个系统架构的通盘考量。

Prototype Pattern 的其他特性:

  • 可避免形成多个类与类之间的大量平行 (平级) 阶层结构,在宽度和深度上的扩展。
  • 「浅拷贝」可以提供低成本的对象复制;但通过「序列化 (Serialization)」进行的「深拷贝」代价就比较大,而非低成本的。

 

--------------------------------------------------------

本帖的最后,提供一位 Java 大师 - 结城浩,所绘制的 Prototype Pattern 趣味四格漫画,原地址如下:

Giko 猫谈 DP 四格漫画:
http://www.javaworld.com.tw/jute/post/view?bid=44&id=40932&sty=3&age=0&tpg=1&ppg=1#40932
http://www.hyuki.com/dp/cat_Prototype.html


∧_∧  敲敲敲  ╱
(    )  ∧ ∧ < 等于是利用 copy & paste 来制作实例..恩....。
(    )  (,,゚Д゚)  ╲____________
______ (つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
____________

| 喔~、是 Prototype Pattern 吗?
╲ __ __________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 你你是谁? ...有..有什么事嬷你?...
  (  ⊃ )  (゚Д゚;)  ╲____________
________(つ_つ____
|    日∇ ╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 利用实例来创建实例..恩
╲ __ ________
  |╱
  ∧_∧       ╱
  ( ・∀・)  ∧ ∧ < 恩..可以那样说...类变成配角.
 (     )  (;゚Д゚)  ╲____________
_____ (つ_つ____
|     日∇╲|ThinkPad|╲
|       =========  ╲

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
__________

| 把类的设计失败, 在实例去修正.恩.
╲ __ ________
  |╱
 ∧_∧       ╱
 ( ・∀・)  ∧ ∧ < 不..不是这样啦..
 (  ⊃ )  (゚Д゚;) ╲____________
_____(つ_つ____
|   日∇ ╲|ThinkPad|╲
|      =========  ╲
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

giko猫.clone()

    ∧ ∧        ┌────────────────
   ( ゚Д゚ )       < giko.clone()
    U  U        └────────────────
     |  |
    U U

    ∧ ∧ ∧      ┌────────────────
   ( ゚Д ゚Д゚ )     < 这这是、
    U  U  U      .└────────────────
     |    |
    U U U

    ∧ ∧∧∧     ┌────────────────
   ( ゚Д゚ ゚Д゚ )    < 到底到底是、
    U U.U  U     └────────────────
     |     |
    .U UU U

    ∧ ∧   ∧ ∧   ┌────────────────
   ( ゚Д゚ >< ゚Д゚ )  < 是 shallow copy 是 shallow copy 吗
    U  U  U  U   └────────────────
     |   ><  |
    .U U   U U

.    ∧ ∧   ∧ ∧  ┌────────────────
    ( ゚Д゚ ) * ( ゚Д゚ ) < 或是 deep copy。或是 deep copy。
    U  U   U  U  └────────────────
     |  .| .* |  |
     U U   .U U

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

--------------------------------------------------------


本帖的内容和四个示例,已过滤掉 Prototype Pattern 非必要的功能和角色,主要目的是让初学者能够快速入门。实务上 Prototype Pattern 还能有很多变化和进阶应用,比如:

  • 整合「工厂模式」,用一个工厂类来单独负责构造的工作 [8], [19]。
  • 通过「序列化 / 反序列化」实现深拷贝 [2], [13], [14], [20]。
  • 客户端程序多通过一个 Prototype manager 去创建 ConcretePrototype 对象,避免客户端程序与 ConcretePrototype 类产生依赖 [4], [5], [14], [15], [20], [21]。

有兴趣深入研究的网友,可参阅下方的「相关文章」和「相关书籍」。

--------------------------------------------------------

相关文章:

[1] .NET深入学习笔记(4) 深拷贝与浅拷贝(Deep Copy and Shallow Copy) - 老徐的博客 - 博客园
http://www.cnblogs.com/frank_xl/archive/2009/02/24/1396903.html

[2] C# Tips 浅拷贝和深拷贝 - SQL SERVER - 数据库-数据仓库 - ZDNetChina中文社区
http://bbs.zdnet.com.cn/thread-1426176-1-1.html

[3] 小议 .NET 中的对象拷贝 - TerryLee's Tech Space - 博客园
http://terrylee.cnblogs.com/archive/2006/01/06/312493.html

(推荐此帖)
[4] C#设计模式(9)-Prototype Pattern - First we try, then we trust - 博客园
http://www.cnblogs.com/zhenyulu/articles/39257.html

[5] Prototype Design Pattern in C# and VB.NET (英文)
http://www.dofactory.com/Patterns/PatternPrototype.aspx

[6] Proxy 模式,作者: caterpillar (繁体中文)
http://caterpillar.onlyfun.net/Gossip/DesignPattern/PrototypePattern.htm
http://www.javaworld.com.tw/jute/post/view?bid=44&id=25500&sty=1&tpg=3&age=-1

[7] 原型模式(ProtoType) - 最简单的 - JavaEye技术网站
http://iwtxokhtd.javaeye.com/blog/361086
http://www.codeweblog.com/prototype-model-prototype/

[8] 设计模式学习笔记五——Prototype模式 - 每天进步一点点,微笑面对全世界! - JavaEye技术网站
http://mybluesky99.javaeye.com/blog/384252

[9] Prototype Design Pattern :: BlackWasp Software Development (英文)
http://www.blackwasp.co.uk/Prototype.aspx

[10] 设计模式学习笔记(六)——Prototype原型模式 - KiddLee - 博客园
http://www.cnblogs.com/kid-li/archive/2006/05/18/403559.html

[11] Prototype pattern - Wikipedia, the free encyclopedia (英文)
http://en.wikipedia.org/wiki/Prototype_pattern

[12] DotNet Framework源代码中的模式(六)——Prototype(原型模式) - Guushuuse _NET - 博客园
http://www.cnblogs.com/guushuuse/archive/2009/05/15/1457951.html

[13] 无废话C#设计模式之五:Prototype - LoveCherry - 博客园
http://www.cnblogs.com/lovecherry/archive/2007/10/06/915535.html

[14] .NET设计模式(6):原型模式(Prototype Pattern) - TerryLee's Tech Space - 博客园
http://www.cnblogs.com/Terrylee/archive/2006/01/16/317896.html

[15] ASP_NET Wiki Architecture Design Patterns (英文)
http://wiki.asp.net/page.aspx/499/prototype-pattern/
http://wiki.asp.net/page.aspx/276/design-patterns/

[16] Implementing Audit / history tracking using Prototype Pattern (英文)
http://www.codeproject.com/KB/aspnet/AuditTracking.aspx

目录
相关文章
|
移动开发 算法 Linux
艾伟:C# Design Patterns (2) - Strategy
Strategy Pattern (策略模式) 所谓 Strategy Pattern 的精神,就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用;而不是将策略、具体的算法和行为,硬编码在某个类或客户程序中,导至事后的修改和扩展不易。
1019 0
|
C#
艾伟:C# Design Patterns (1) - Factory Method
Simple Factory Pattern (简单工厂模式) 特性: 把类的实例化工作,集中到一个「工厂类」去处理,亦即将 new instance 的工作,都交给一个「工厂」去处理,而不要分散写在各个类中。
889 0
|
C#
艾伟_转载:C# Design Patterns (1) - Factory Method
Simple Factory Pattern (简单工厂模式) 特性: 把类的实例化工作,集中到一个「工厂类」去处理,亦即将 new instance 的工作,都交给一个「工厂」去处理,而不要分散写在各个类中。
992 0
|
移动开发 算法 Linux
艾伟_转载:C# Design Patterns (2) - Strategy
Strategy Pattern (策略模式) 所谓 Strategy Pattern 的精神,就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用;而不是将策略、具体的算法和行为,硬编码在某个类或客户程序中,导至事后的修改和扩展不易。
941 0
|
数据库连接 C# 数据库
艾伟_转载:C# Design Patterns (4) - Proxy
本帖介绍 Proxy Pattern (代理模式)。Proxy Pattern (代理模式)The Proxy Pattern provides a surrogate or placeholder for another object to control access to it...                                  - Design Patterns: Elements of Reusable Object-Oriented Software在 GoF 的书中,对 Proxy 模式的定义为:替某个对象,提供一个替身,以控制外界对这个对象的访问。
1253 0
|
C#
C#设计模式之五原型模式(Prototype Pattern)【创建型】
原文:C#设计模式之五原型模式(Prototype Pattern)【创建型】 一、引言      在开始今天的文章之前先说明一点,欢迎大家来指正。很多人说原型设计模式会节省机器内存,他们说是拷贝出来的对象,这些对象其实都是原型的复制,不会使用内存。
1715 0
|
C# 设计模式 索引
乐在其中设计模式(C#) - 原型模式(Prototype Pattern)
原文:乐在其中设计模式(C#) - 原型模式(Prototype Pattern)[索引页][源码下载] 乐在其中设计模式(C#) - 原型模式(Prototype Pattern) 作者:webabcd 介绍 用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
860 0
|
C# 设计模式
C#设计模式(6)——原型模式(Prototype Pattern)
原文:C#设计模式(6)——原型模式(Prototype Pattern) 一、引言 在软件系统中,当创建一个类的实例的过程很昂贵或很复杂,并且我们需要创建多个这样类的实例时,如果我们用new操作符去创建这样的类实例,这未免会增加创建类的复杂度和耗费更多的内存空间,因为这样在内存中分配了多个一样的...
1042 0