用设计模式固化你的C#程序.doc_第1页
用设计模式固化你的C#程序.doc_第2页
用设计模式固化你的C#程序.doc_第3页
用设计模式固化你的C#程序.doc_第4页
用设计模式固化你的C#程序.doc_第5页
已阅读5页,还剩11页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

用设计模式固化你的C#程序【概要:通过提供一个框架,设计模式可以解决应用开发中的许多问题。模式使得设计过程更加清晰高效,它特别适用于C#程序开发,因为C#是面向对象的语言。【译注:因为设计模式的由来和出发点就是描述面向对象的(可复用的)软件设计】现有的设计模式为你自己的类的设计提供了优秀的模板,使用模式可以缩短软件开发周期。本文将描述几个流行的设计模式,包括singleton、strategy、decorator、composite和state,你可以在你自己的应用中使用它们,藉此提高应用的扩展性,并使类更易于重用。】正如任何一个老练的面向对象的软件开发者所了解的那样,缺乏对设计模式最起码的了解而来讨论软件设计架构是不可思议的。如果不是全部那也有大多数的软件应用、工具和系统至少使用了一种甚至更多种设计模式。设计模式是一种对一套相互作用的类的描述,这些类为解决特定上下文环境中的一般性问题提供了框架。换句话说,模式为面向对象软件开发中的特定问题提供了解决方案。此外,模式一般也重视限制其适应解决方案的相关约束和其它因素。类和类之间的连接和通信以及上下文细节共同定义了一个模式,它为任何一个面向对象软件设计中在特性和必要条件方面与之匹配的问题提供了解决方案。我必须承认我是设计模式的一个热心的支持者。自从我阅读了Gamma、Helm、Johnson和Vlissides合著的那本创造性的著作设计模式以来,我就很少不用任何模式而设计软件了。实际上,我在软件设计的早期阶段花了相当可观的时间来定夺可和将来架构自然吻合的模式。毕竟,模式是经过时间和应用领域考验过的对一些问题的解决方案,那些问题已经被经验丰富的设计师、开发者和语言专家所解决。对任何一个正在进行软件设计的人员来说,善用可加以利用的知识和专家经验是明智的。而采用一个已被反复证明是成功的解决方案而不是从头发明一个新的往往是个好主意。 几乎没有开发人员能够享受只写小程序的奢侈了。现代的应用软件和系统是复杂的,它们往往由成千上万行代码组成,并且在这些基础代码之上的代码甚至更为庞大。仅仅对工具和语言的简单掌握是不足以胜任程序设计要求的,公司软件开发一般都要求在设计和架构上具有极大的弹性,以适应在产品开发的不同阶段客户的不断变化的需求,甚至在产品发布后也常常如此。这种动态性要求软件设计必须强健。它应该能够适应变化并且不会带来任何不必要的连锁反应不应该要求重写潜在的、不相干的(子)系统。向不具备扩展能力的模块添加特性和组件是令人沮丧的和难以达到预期目标的。封闭的、无弹性的设计迟早会被变化的压力所压垮。设计模式有助于弹性架构的基础铺设,而这,是每一个优秀的面向对象设计的共同特点。 设计模式已经被编目归类以用于解决从细小问题乃至大规模架构级问题。本文将介绍几个流行的设计模式,在我自己的项目里,我发现它们很有用。尽管熟悉面向对象设计的概念有助于理解本文,但我并不假定你具备任何设计模式的预备知识。尽管任何适宜于面向对象开发的程序语言都可以用来阐明模式,但我将只用C#来编写例子,也借此来展示这门语言的威力。我不会讨论任何微软.NET类库细节,相反,我将集中于使用C#语言作为设计面向对象软件的工具。C#和设计模式 C#是一个现代的程序语言,它通过提供直接映射面向对象设计概念的句法结构和语义支持来促进面向对象软件开发。这和C+大不相同,C+同时支持面向过程、面向对象和泛型编程。虽然如此,如果你是一名C+程序员,跟进C#是非常容易的。对于C+程序员来说,这个学习曲线是相当平坦的。即使你以前从未看过任何C#代码,理解本文示例代码也不应该有任何问题。事实上,如果你发现C#对设计模式的实现更为清晰,我也不会有任何惊讶,特别是如果你以前使用设计模式编写过代码的话。一般讨论设计模式的书籍和文章都会详细地描述模式所要解决的问题和上下文细节,并随后提供一个规范的解决方案的描述。本文不会那么严谨,我只关注模式本质,并辅以适当的C#示例来加以说明。让我们从最简单的设计模式开始:singleton。 singleton 任何编写过MFC应用的开发人员(不管编写的应用是如何的小)都知道什么是singleton。singleton是类的唯一实例。使用MFC时,从CWinApp派生的应用类的全局实例就是singleton。当然,在MFC应用中,尽管规定不允许创建应用类的第二个实例,但是并没有什么可以阻止你那么做。【译注:实际上,不管是VC6.0还是VC7.0Beta2,它们的编译器都可以一定程度地限制你创建第二个实例。之所以说一定程度上,是因为诸如这种情况编译器并不帮你检查试图在窗体的某个按钮事件里创建应用类的第二个实例】在这种情况下,当你需要某个特定的类表现出singleton行为时,一个更好的替代方案是让这个类自己负责确保只会被创建一个并且只有一个实例。再回到MFC,我们知道保证应用类实例的唯一性的责任被留给了开发应用的程序员,他(她)们必须小心不要创建应用类的第二个实例。 现在来看看表1所示的类。singleton的访问被局限于必须通过静态方法Instance。多数情况下,singleton应该具有全局可见性,这可通过将其创建方法public来实现。和用全局变量模拟singleton不同,这种模式可以防止创建出多余的实例,同时兼具全局可见性。注意,该类的构造器被置为private,这就意味着没有任何办法可以绕过静态方法Instance来直接创建类的实例。 表1 class Singleton private static Singleton singleton = null; public static Singleton Instance() if (null = singleton) singleton = new Singleton(); return singleton; private Singleton() singleton模式的作用还不止于此,尤其是可以将其扩展,以创建类的可变数量的实例。假定有一个应用,当需要执行特定任务时就需要调度一个工作者线程。考虑到节约系统资源,我们使用singleton来实现这个线程类。不久,需要singleton线程处理的任务变得密集起来,如果我们决定扩展这个应用,我们可以很方便地增加工作者线程的数量,因为线程的创建和对它们的访问授权的所有逻辑都被定义在一个类中。 singleton模式的另外一个优点是singleton的创建可以被延迟到真正需要的时候,正如表1所示。不管是否需要,全局变量一开始就被创建,但这个全局对象并不一定是一直都需要的。C#不支持全局变量,但还是有可能在某个方法的一开始就在堆上创建了一个对象并直到很久以后才使用它。果真如此的话,singleton模式为这种案例提供了一个优雅的解决方案。另外,作为一个工具,在singleton模式的实现上,C#优于C+,尽管这个优点很微妙,但绝对重要。基于C+的实现需考虑singleton带来的一些和生命期管理有关的棘手问题,而在C#中则由运行时自动处理。这个优点是有意义的,在singleton模式的C#实现版本中,你只需保证在需要singleton的时候,你拥有一个活的引用即可。 以下是singleton模式完整示例 C#示例: using System; class Singleton private static Singleton singleton = null; public static Singleton Instance() if (null = singleton) singleton = new Singleton(); return singleton; private Singleton() class Application public static void Main() Singleton s1 = Singleton.Instance(); /Singleton s2 = new Singleton(); /错误:构造器不可访问 Singleton s2 = Singleton.Instance(); if (s1.Equals(s2) / 引用相等 Console.WriteLine(Instances are identical); /*以下是程序输出结果: Instances are identical */ strategy 应用常因用户输入、运行平台和部署环境等的不同执行的任务亦不相同。以磁盘文件异步I/O举例,Windows NT/2000下的Win32 API对异步I/O提供了原生支持,但Windows 95/98并非如此。因此,依赖于异步文件I/O的应用就必须实现两套不同的算法,根据部署环境不同,一套可使用Win32 API,另一套可能要采用多线程技术从头写起。而对于客户来说一般不会意识到执行了不同算法,他(她)们所关心的只是得到同样的结果,他(她)们也只关心这个。另一个例子是从互联网上的远程服务器下载文件。提供文件下载服务的应用接受一个URL和一个协议(如FTP或HTTP),然后创建一个使用该协议和远程服务器通讯的对象。根据用户不同输入,使用不同的算法(协议)。但结果是一样的:下载了一个文件。让我们看一个更具体的例子:素数测试。表2所示的代码声明了一个接口(C#中的一个概念),它只有一个方法:IsPrime。 表2 interface Strategy bool IsPrime(int n); 接口就象一个合约,它是所有派生类必须遵从的规范。更为特别的是,它定义方法的签名但并不实现它们,实现接口的具体类必须提供这些方法的实现。在这一点上,C#明显优于C+,因为C+缺乏对接口在语言上的原生的支持。C+程序员一般是通过定义只包含纯虚成员函数的的抽象类来创建接口。在C#中,所有接口成员都是public的,所有实现接口的类都必须实现接口中所有方法。现在假定我们有三个不同的素数测试算法,每一种都有自己的性能和精度指标。其中之一属于计算密集型,对因数进行彻底测试,另外一种算法速度较快但对大数的测试结果未必准确。我的应用就是要询问用户期望何种执行性能,然后根据其选择调用相应的算法。我把算法封装到实现Strategy接口的若干个类中。参见表3示例代码。 表3 class Fermat : Strategy public bool IsPrime(int n) bool result = false; /使用Fermat法测试n是否为素数,果真,则更新result的值 Console.WriteLine(Using Fermats test); return result; 采用这种方式实现所有三个算法后,我就能够采用一种和任何特定算法实现细节毫无耦合的方式来设计客户程序。客户持有一个接口引用,并且不必知道该接口具体实现的任何细节。参见表4代码。 表4 class Primality private Strategy strategy; public Primality(Strategy s) strategy = s; public bool Test(int n) return strategy.IsPrime(n); 最后,我创建了一个Primality类的实例,根据用户输入,以相应算法对其进行初始化。Primality类的Test方法调用相应的算法对象(实现Strategy接口的对象)的IsPrime方法。 用这种方式构造算法族有很多优点,但最大的优点还是客户程序同特定算法实现细节毫无耦合关系。这提高了扩展性可以开发别的算法并将其无缝插入,只要它们遵从基本接口规范。这样就可以动态变换算法。而且,strategy模式还避免了因为使用条件语句而使客户程序代码变得混乱的可能性。以下是strategy模式完整示例 C#示例: using System; interface Strategy bool IsPrime(int n); class Miller : Strategy public bool IsPrime(int n) bool result = false; /使用Miller法测试n是否为素数,果真,则更新result值 Console.WriteLine(Using Millers algorithm); return result; class Fermat : Strategy public bool IsPrime(int n) bool result = false; /使用Fermat法测试n是否为素数,果真,则更新result值 Console.WriteLine(Using Fermats algorithm); return result; class Mersenne : Strategy public bool IsPrime(int n) bool result = false; /使用Mersenne法测试n是否为素数,果真,则更新result值 Console.WriteLine(Using Mersennes algorithm); return result; class Primality private Strategy strategy; public Primality(Strategy s) strategy = s; public bool Test(int n) return strategy.IsPrime(n); class Application public static void Main() Console.Write(Number to be tested: ); string input = Console.ReadLine(); int n = Int32.Parse(input); Console.Write(Desired algorithm performance: lo, medium, hi? ); input = Console.ReadLine(); char ch = char.Parse(input); Primality prime = null; switch (ch) case l: case L: prime = new Primality(new Miller(); break; case m: case M: prime = new Primality(new Fermat(); break; case h: case H: prime = new Primality(new Mersenne(); break; if (prime != null) bool result = prime.Test(n); else Console.WriteLine(Bad Choice!); /*以下是某次测试输出结果: Number to be tested:1 Desired algorithm performance: lo, medium, hi? M Using Fermats algorithm */ decorator 客户应用常常需要加强某些类的方法所提供的服务。可能需要分别在方法调用前后插入一些预先处理和后继处理的代码。要实现这个目标,一种办法是干脆做成不同的方法调用。然而,这种方式不但麻烦,而且不利于框架的扩展。例如,如果对于不同的客户来说明显要执行不同的预先处理和后继处理任务,应用程序逻辑将会因为条件语句而变得晦涩不清并难以维护。问题是如何才能增强类所提供的功能的同时又不影响客户代码,而Decorator模式正是所需。 让我们来考察一个具有远程文件传输功能的类的例子。这样的类代码可能如表5所示。 表5 class FileTransfer public virtual void Download(string url, byte data, int size) / 下载文件 public virtual void Upload(string url, byte data, int size) / 上传文件 假定有一个客户程序对这个功能感兴趣。除了能够上传和下载文件外,客户应用还希望能够将所有的文件传输请求和执行访问检查写入日志。基于decorator模式的一种实现方式是从FileTransfer类派生出一个类并重载虚方法,并于调用基类方法之前或之后,插入附加代码。如表6所示。 表6 class Decorator : FileTransfer private FileTransfer ft = new FileTransfer(); private bool IsAccessAllowed(string url) bool result = true; / 决定是否对请求的URL访问授权 return result; private void LogAccess(string url) / 将URL、时间、用户身份等信息写入数据库 Console.WriteLine(Logging access to 0, url); public override void Download(string url, byte data, int size) if (!IsAccessAllowed(url) return; ft.Download(url, data, size); LogAccess(url); 客户程序可以继续使用同样的接口【译注:并非C#语义的接口】进行工作。实际上,还可以进一步改进这个方案,可将FileTransfer类和Decorator类改为实现同一个具有Upload和Download方法的接口的类。如此,就可以使客户程序只依照接口工作,而同具体实现彻底解耦。 当一定范围的扩展和任务可以在现有类的基础上进行,并且将所有扩展都定义为类是不切实际的情况下,使用decorator模式可以动态、透明地添加或移去功能而不会影响客户代码。 以下是decorator模式完整示例 using System; class FileTransfer public virtual void Download(string url, byte data, int size) / 下载文件 public virtual void Upload(string url, byte data, int size) / 上传文件 class Decorator : FileTransfer private FileTransfer ft = new FileTransfer(); private bool IsAccessAllowed(string url) bool result = true; / 决定是否对请求的URL访问授权 return result; private void LogAccess(string url) / 将URL、时间、用户身份等信息写入数据库 Console.WriteLine(Logging access to 0, url); public override void Download(string url, byte data, int size) if (!IsAccessAllowed(url) return; ft.Download(url, data, size); LogAccess(url); public override void Upload(string url, byte data, int size) if (!IsAccessAllowed(url) return; ft.Upload(url, data, size); LogAccess(url); class Application public static void Main() Console.Write(Enter URL to access: ); string url = Console.ReadLine(); Console.Write(Enable logging and access check? ); string input = Console.ReadLine(); char ch = char.Parse(input); bool decoration = (ch = y | ch = Y); FileTransfer ft = null; if (!decoration) ft = new FileTransfer(); else ft = new Decorator(); byte buf = new byte1024; ft.Download(url, buf, 1024); /*以下是某次运行时输出结果: Enter URL to access: Enable logging and access check? Y Logging access to */ composite 当需要以一致的方式处理聚集对象和个体对象时,composite模式就派上了用场。【译注:此“聚集”并非COM语义的聚集】一个常见的例子是列举文件夹内容。文件夹可能不单包括文件,也可能有子文件夹。递归列举某个顶层文件夹的应用可以使用条件语句来区分文件和子文件夹,并可通过遍历目录树来打印出子文件夹中的所有文件。对该问题的一个更好的解决方案是使用composite模式。使用这种方式,文件夹内的每一种项目,不管是文件、子文件夹、网络打印机或任何一种目录元素的别名,都是遵从同样接口的某个类的实例,该接口提供某个方法来描述这些元素的用户友好的名称。如此,客户应用就不必区分每一种不同的元素,这也降低了应用逻辑的复杂性。 另一个例子,也是我下面要用C#语言展现的,是一个画图应用。它从对象数据库中提取基本的和组合的图形元素,并将它们画在画布上。假定数据库可以容纳Line、Circle和Drawing(包容有Line和Circle)。让我们看看如表7所示的接口。 表7 interface Shape void Draw(); 接口Shape有一个方法Draw。诸如Line之类的简单图形对象可以实现该接口,重载方法Draw【译注:对于C#中的interface及其实现,并不需要virtual或override关键字,故与其说是“重载”,不如说是实现】,以在画布上画线。参见表8。 表8 class Line : Shape private double x1, y1, x2, y2; public Line(double x1, double y1, double x2, double y2) this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; public void Draw() / 从(x1, y1) 到(x2, y2)画一条线 Circle类和Line类类似。为了能够一致地处理聚集类和简单实体类,聚集对象也应该实现Shape接口。Drawing是图形对象的集合类,它实现了Draw方法,列举出其容纳的所有基本图形对象并将它们一一画出。表9的代码展示了其工作原理。 表9 class Drawing : Shape private ArrayList shapes; public Drawing() shapes = new ArrayList(); public void Add(Shape s) shapes.Add(s); public void Draw() IEnumerator enumerator = shapes.GetEnumerator(); while (enumerator.MoveNext() (Shape) enumerator.Current).Draw(); 注意,客户无需关心某个图形对象属于基本类型和还是集合类型。横跨于它们之上的公共接口【译注:此处即是Shape】使得我们可以向其中添加新对象【译注:新类型的对象】而不会对客户程序产生任何影响。通过公共接口,composite模式避免了客户程序必须区分个体对象和容器对象,这在相当大的程度上促进了代码重用并简化了客户程序逻辑。 注意,Drawing类的Draw方法使用了System.Collections名字空间中的类。如欲了解类库更多知识,请参阅.NET Framework SDK的有关文档。 以下是composite模式完整示例 using System; using System.Collections; interface Shape void Draw(); class Line : Shape private double x1, y1, x2, y2; public Line(double x1, double y1, double x2, double y2) this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; public void Draw() /从(x1, y1) 到(x2, y2)画一条线 Console.WriteLine(Drawing a line); class Circle : Shape private double x, y, r; public Circle(double x, double y, double radius) this.x = x; this.y = y; this.r = r; public void Draw() /以(x, y)为圆心,r为半径画一个圆 Console.WriteLine(Drawing a circle); class Drawing : Shape private ArrayList shapes; public Drawing() shapes = new ArrayList(); public void Add(Shape s) shapes.Add(s); public void Draw() IEnumerator enumerator = shapes.GetEnumerator(); while (enumerator.MoveNext() (Shape) enumerator.Current).Draw(); class Application public static void Main() Shape array = new Shape3; array0 = new Line(0, 0, 10, 12); array1 = new Circle(2, 3, 5.5); Drawing dwg = new Drawing(); dwg.Add(new Line(3, 4, 3, 5); dwg.Add(new Circle(5, 6, 7.7); array2 = dwg; / 画出所有的图形,注意:用一致的方式来访问所有对象 for (int i = 0; i 3; +i) arrayi.Draw(); /*以下是程序输出结果: Drawing a line Drawing a circle Drawing a line Drawing a circle */State以下是state模式完整示例 using System; abstract class State public virtual void AddNickel(VendingMachine vm) public virtual void AddDime(VendingMachine vm) public virtual void AddQuarter(VendingMachine vm) protected virtual void ChangeState(VendingMachine vm, State s) vm.ChangeState(s); class VendingMachine private State state; public VendingMachine() Console.WriteLine(The Vending Machine is now online: product costs 25c); state = Start.Instance(); public void ChangeState(State to) state = to; public void Vend() / 发饮料 Console.WriteLine(Dispensing product.Thank you!); public void AddNickel() state.AddNickel(this); public void AddDime() state.AddDime(this); public void AddQuarter() state.AddQuarter(this); class Start : State private static State state = new Start(); private Start() public static State Instance() / singleton逻辑 Console.WriteLine(Credit: 0c); return state; public override void AddNickel(VendingMachine vm) ChangeState(vm, Five.Instance(); public override void AddDime(VendingMachine vm) ChangeState(vm, Ten.Instance(); public override void AddQuarter(VendingMachine vm) vm.Vend(); class Five : State private static State state = new Five(); private Five() public static State Instance() / singleton 逻辑 Console.WriteLine(Credit: 5c); return state; public override void AddNickel(VendingMachine vm) ChangeState(vm, Ten.Instance(); public override void AddDime(VendingMachine vm) ChangeState(vm, Fifteen.Instance(); public override void AddQuarter(VendingMachine vm) vm.Vend(); ChangeState(vm, Start.Instance(); / no change returned :- class Ten : State private static State state = new Ten(); private Ten() public static State Instance() / singleton 逻辑 Console.WriteLine(Credit: 10c); return state; public override void AddNickel(VendingMachine vm) ChangeState(vm, Fifteen.Instance(); public override void AddDime(VendingMachine vm) ChangeState(vm, Twenty.Instance(); public override void AddQuarter(VendingMachine vm) vm.Vend(); ChangeState(vm, Start.Instance(); / no change returned :- class Fifteen : State private static State state = new Fifteen(); private Fifteen() public static State Instance() / singleton 逻辑 Console.WriteLine(Credi

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论