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

本帖介绍 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

时间: 2024-11-10 07:11:17

艾伟_转载:C# Design Patterns (5) - Prototype的相关文章

艾伟_转载: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 在 Go

艾伟_转载:C# Design Patterns (3) - Decorator

Decorator Pattern (装饰模式) 装饰模式可「动态」地给一个对象添加一些额外的职责,提供有别于「继承」的另一种选择.就扩展功能而言,Decorator Pattern 透过 Aggregation (聚合) 的特殊应用,降低了类与类之间的耦合度,会比单独使用「继承」生成子类更为灵活. 一般用「继承」来设计子类的做法,会让程序变得较僵硬,其对象的行为,是在「编译」时期就已经「静态」决定的,而且所有的子类,都会继承到相同的行为:然而,若用「装饰模式」以及 UML 的 Aggregat

艾伟_转载:C# Design Patterns (2) - Strategy

Strategy Pattern (策略模式) 所谓 Strategy Pattern 的精神,就是将策略 (算法) 封装为一个对象,易于相互替换,如同 USB 设备一样可即插即用:而不是将策略.具体的算法和行为,硬编码在某个类或客户程序中,导至事后的修改和扩展不易. 若有多种「策略」,就将这些个策略,和这些策略的算法.行为,封装在各个类中,并让这些类,去继承某个公用的抽象类或接口.接着在客户程序中,就可动态引用,且易于更换这些不同的「策略」,不会因为日后添加.修改了某一个「策略」,就得重新修改

艾伟_转载:C# Design Patterns (1) - Factory Method

Simple Factory Pattern (简单工厂模式) 特性: 把类的实例化工作,集中到一个「工厂类」去处理,亦即将 new instance 的工作,都交给一个「工厂」去处理,而不要分散写在各个类中. 客户端程序,与创建实例 (对象) 的工作必须隔离,亦即「解耦」,客户端程序只要专注于自己的业务逻辑.适用于客户端程序在开发过程中,尚无法预知要创建的具体类型. 产品具体的实现能和客户端隔离,便于事后抽换. Simple Factory Pattern (简单工厂模式).Factory M

C# Design Patterns (5) - Prototype

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

艾伟_转载:虚方法的使用

<编程絮语>之一 C#的语法脱胎于C++,因而保留了virtual关键字,可以定义一个虚方法(或虚属性).一个类的成员被定义为virtual,就意味着它在告诉自己的子类:我准备了一笔遗产,你可以全盘接受,也可以完全拒绝或者修改我的遗嘱.显然,虚方法授予子类的权利甚至大于抽象方法.子类面对抽象方法只有重写(override)的权利,而对于虚方法,它还可以选择完全继承. 毫无疑问,虚方法破坏了对象的封装性.如果不加约束的使用,会对调用方造成破坏,至少它有可能破坏子类与父类之间在外在行为上的一致性.

艾伟_转载:.NET设计模式:单件模式(Singleton Pattern)

概述 Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点.这就提出了一个问题:如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?客户程序在调用某一个类时,它是不会考虑这个类是否只能有一个实例等问题的,所以,这应该是类设计者的责任,而不是类使用者的责任. 从另一个角度来说,Singleton模式其实也是一种职责型模式.因为我们创建了一个对象,这个对象扮演了独一无二的角色,在这个单独的对象实例中,它集中了它所属类的所有权力,同时它也肩负了行使这种权力的职责! 意图

艾伟_转载:.NET设计模式:原型模式(Prototype Pattern)

概述 在软件系统中,有时候面临的产品类是动态变化的,而且这个产品类具有一定的等级结构.这时如果用工厂模式,则与产品类等级结构平行的工厂方法类也要随着这种变化而变化,显然不大合适.那么如何封装这种动态的变化?从而使依赖于这些易变对象的客户程序不随着产品类变化? 意图 用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象. 结构图 Prototype模式结构图 生活中的例子 Prototype模式使用原型实例指定创建对象的种类.新产品的原型通常是先于全部产品建立的,这样的原型是被动的,并不

艾伟_转载:数组排序方法的性能比较(上):注意事项及试验

昨天有朋友写了一篇文章,其中比较了List的Sort方法与LINQ中排序方法的性能,而最终得到的结果是"LINQ排序方法性能高于List.Sort方法".这个结果不禁让我很疑惑.因为List.Sort方法是改变容器内部元素的顺序,而LINQ排序后得到的是一个新的序列.假如两个排序方法的算法完全一致,LINQ排序也比对方多出元素复制的开销,为什么性能反而会高?如果LINQ排序的算法/实现更为优秀,那为什么.NET Fx不将List.Sort也一并优化一下呢?于是今天我也对这个问题进行了简