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

时间: 2024-10-15 17:18:14

C# Design Patterns (5) - Prototype的相关文章

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

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

《Design Patterns》学习总结

在时间比较空余的时候,又找到一本一直想看的书,就是这本名为<Design Patterns>(Gang of Four)的著作.本书通过类似Window Doc的程序,来揭开设计模式学习序幕,通过分析设计程序时遇到的困难,引出可以解决问题的设计模式,从而引导你更全面的掌握设计模式.案例程序的设计引出8个设计模式,案例程序设计完成后就是单独的23个经典设计模式,可以单个查阅,单个阅读. Abstract Factory模式 这个模式似乎离我比较远,因为本人还从未开发过在此模式讲解过程中介绍到的设

Head First Design Patterns

    在更大的计划之前,先温习一下Design Pattern的功课.    看了<Head First Design Patterns>里讲Decorator的样章,发现JOLT大奖不是白拿的,叙事能力之强,表达之清晰,不是那些满腹经伦的老先生可以比的.而且整个Pattern的讲述过程循序渐进,真的可以保证--小白都能学会设计模式.    可惜就只有样章.Head First系列的电子书都不好找,只好还是翻出老先生们的书来看.    这次温习很快做完,其实GOF80%的模式,都是基于一个原

Design Patterns: Solidify Your C# Application Arch

Design Patterns: Solidify Your C# Application Architecture with Design Patterns中文版(下篇)    optimizer(翻译)   关键字     设计模式 singleton strategy decorator composite state   出处     http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnmag01/html

Mobile UI Design Patterns: 10+ Sites for Inspiration

原文:http://sixrevisions.com/user-interface/mobile-ui-design-patterns-inspiration/ User interface design patterns are solutions to common design challenges, such as navigating around an app, listing data or providing feedback to users. Mobile apps and

Comparing Two High-Performance I/O Design Patterns

Summary This article investigates and compares different design patterns of high performance TCP-based servers. In addition to existing approaches, it proposes a scalable single-codebase, multi-platform solution (with code examples) and describes its

C# Design Patterns (1) - Factory Method

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

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 (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