导读
本书的应用背景
面向对象设计模式,也就是本书简称的“设计模式”,是软件设计过程中,通过面向对象的方法对相近似的问题,在指定上下文范围内给予的指导性解决方案。模式的主要价值在于它们是以往经验的浓缩,尤其在我们建立复杂系统的时候,借鉴和采用模式可以让我们少走弯路,其设计比较灵活并具有不错的扩展性。
和15年前相比,现在的开发工作更强调对于业务变化的适应性。虽然有各种方法学帮助我们以尽量小的代价适应这些变化,但相信没有多少人愿意对一个已经基本完成的系统进行结构性设计修改,即便修改也希望尽可能地集中在一个点上。但现实的情况总和我们作对:业务实体这个本应该相对稳定的对象,在信息化快速建设过程中被赋予了多变的特质;业务流程和操作功能总是进行着“家常”式的变化;为了适应更广泛的服务对象,我们开发的产品需要不断集成更多的第三方产品、需要支持更多的IT 产品;最后,还有会更加多变的未来。技术上虽然各种开发方法、架构技术都试图让80%的开发人员仅仅关注业务逻辑的实现,但很多情况下这都是美好的愿景。简言之,开发人员处在一个夹缝之中,如果不尽量让自己的设计更具弹性,则很容易让本已经满负荷的工作不断加码。
对于准一次性代码而言,应用设计模式常常会成为负担;但对于公共库、公共平台、领域通用软件而言,合理使用设计模式则是避免“坏”设计的一个有效途径:
● 避免僵化。
● 增加重用性。
● 以适度的复杂性应对可预见的变化。
因此,如果您要“坚守”某一块代码,而且该代码总是受到打压不得不适应各种变化的话,您可以在抽象的基础上发掘变化的诱因,如果需要,参照贴近的模式设计并实现。那么为什么基于.NET Framework呢?因为.NET Framework已经被MySpace等众多成功的电子商务站点所验证,它完全可以支撑大型应用运行维护;至于选择C#而不是Java和C++,也许是因为C#语言更优美吧。
谁应该读本书
本书的目标读者是对C#语言和.NET Framework平台有一定了解或应用经验的用户,尤其适于那些希望能基于模式技术在设计和开发方面应对更多挑战的用户。对于具有多年项目经验的架构师而言,本书的内容可能有些肤浅,不过您可以把本书当成一个小备忘录。
本书具体读者对象如下:
● 初学设计模式的读者
本书对于您可能有一个小的跨度,不过这并不影响您使用本书。您可以在对 C#语言语法了解的基础上,在第1章中补充一些C#面向对象开发的高级知识,第1章中的依赖注入部分则可以先跳过。第2章还会为您补充一些具体的开发专题,您可以结合自己项目的需要做一套类似的小工具类型。
接着,创建型模式、结构型模式可以作为您树立并强化设计模式思想的途径。
● 中级.NET开发工程师
您已经具有了C#开发的经验,这时候不妨浏览一下第1章的内容,因为它除了进一步强化C#语言对象化特性外,还有一些规范性、扩展性的内容。然后您可以继续学习GOF23。建议您在学习GOF23后,不妨在第26、27章稍作停顿,回顾并对之前的了解做汇总和检查,最好结合项目中一个比较核心的类库(例如:数据访问、报文交接、通知机制……)设计一个自己的Show case,环境可以考虑得复杂些,通过三四个迭代的开发,从中体会出GOF23种主要模式的适用环境。
接着,您可以根据工作的需要,继续学习后面的架构、Web Service模式。
● 高级.NET开发工程师
您已经在.NET 平台完成了一批规模化的项目,这时候您可以浏览本书前面的部分,不过在学习后面的架构模式和Web Service模式前,您不妨在第1章(依赖注入部分)、第4章、第11章、第12章、第14章、第17章、第22章、第24章和第25章的扩展部分稍微多花些时间,因为后续的内容很大程度上与这些章节有关系,而且它们通常在开发技巧上进行了提炼和延伸。
● .NET平台架构师
也许您可以从附录的两章入手,其中一章介绍了领域逻辑的实现技巧(RDBMS/XML DB),另一章介绍了XML应用建模。推荐这两章的目的主要是技术平台,尤其是企业信息本身的变化可能需要我们思路上有些变化。至于前面GOF23的内容,您可能已经烂熟于心,可以先跳过。不过如果有时间,您不妨对第11章、第14章、第17章和第22章中的扩展部分稍微留意一下。接下来,您就可以直接学习后面的架构模式、Web Service模式了。
阅读本书需要的基础知识
Example就够了么
设计模式是一种设计思想,表达这种思想最简洁的方式就是类图,至于说明其实现上的技巧,Example就够了。但Demo和实际工程应用还是有一段差距的,原因不多,但每一个都需要在Example之余好好考虑:
● 数据类型是int、string,还是DateTime?或者是<T>?
● 能支撑负载要求的并发么?
● 是不是应该用Delegate解决常规的异步调用?
● 如何进行运行维护?哪些允许被配置?
● 静态类、匿名方法,还有基于Attribute的开发是不是也可以用于实现设计模式呢?
此外,设计模式的一个亮点就是提高代码的可重用性,如果设计一套比较适合实际工程使用的设计模式库,可以重复八股式反复Example的工作。
设计原则
作为面向对象基本设计原则的忠实体现,设计模式帮助我们在学习过程中不断强化以下五项原则性设计要求。
● 单一职责原则(SRP):一个类应该有且仅有一个引起变化的因素。
● 开放封闭原则(OCP):对扩展开放,对修改封闭。
● Liskov替换原则(LSP):子类可以替换为它的基类。
●依赖倒置原则(DIP):高层模块不应该依赖于低层模块,二者都应该依赖于抽象。抽象不应该依赖于细节。细节应该依赖于抽象。
●接口隔离原则(ISP):一个类对另外一个类的依赖建立在最小的接口上。
而在工程上,SRP和ISP常常被不经意破坏。因为设计时变化因素还没有被真正识别,因此最初设计的接口从最终的实现看,本身是可以分解的;另一个考虑就是为了开发“省事”,参数固定为某个接口,即使以后该接口被丰富了,也不需要修改下游代码。本书由于是专门描述每个设计模式工程化实现的,不涉及上层具体业务处理,因此设计上严格贯彻这五项原则,Test Project设计也依据这五项原则展开。
约定
本书示例全部采用VSTS 2005编写,运行平台为.NET Framework 3.0,对于模式的每个实现的测试均采用VSTS Test Project的Unit Test方式编写。由于VSTS 2005自带的Class Designer对 UML的展示相对很有限,因此笔者选择用IBM Rational Rose 或Sparx的Enterprise Architect绘制UML。本书假设了一个叫“MarvellousWorks”的公司,并按照层次关系设定所有实例的根命名空间为MarvellousWorks.PracticalPattern。此外,示例编码上区别于其他设计模式图书有如下不同。
● 命名规范上依照的是《Design Guidelines for Developing Class Libraries》(.NET Framework 2.0 & 3.0,本书简称为Design Guideline),而不是Java命名法、C++匈牙利命名法。
● 笔者更倾向于用Property,而不是一对set/get方法。
●对于集合类型基于key的检索,笔者喜欢用Indexer而不是类似GetValue(TKey)的方法。
●为了减少使用者的编码量,笔者喜欢将修饰性的类设计为Attribute,而不是单纯的Interface。模式实现上一般也采用Interface<T>到Abstract Class<T>再到Concrete Class的方式,务求在保持抽象性的基础上尽量减少子类实现的编码量。
● 如果某些机制的定义已经在.NET Framework中提供了,那么尽量用平台自己的。
●与扩展无关的、与Assembly内部其他类调用无关的属性、方法等一律声明为private。
● 此外,由于本书很多介绍都是基于代码的说明,为了尽量减少行文中代码的行数,许多接口声明、方法、属性的写法都采用了很不规范的单行书写方式,例如:
C#
using System; namespace MarvellousWorks.PracticalPattern.Concept.Generics { public interface ITarget { void Request();} public interface IAdaptee { void SpecifiedRequest();} public abstract class GenericAdapterBase<T> : ITarget where T : IAdaptee, new() { public virtual void Request() { new T().SpecifiedRequest(); } } }
如果您经常负责代码复查,并且对代码书写方式很敏感,笔者在此对您可能会感到的不畅致歉。
相对于其他介绍设计模式思想的图书,本书重点在于如何借助.NET Framework 2.0 & 3.0平台和C# 2.0实现这些模式,并且根据笔者在工程中遇到的一些情况,介绍如何扩展实现体系。
本书如何组织
本书主要基于C# 2.0的语法(但在部分内容上采用.NET 3.0扩展的部分新增语法),试图将GOF 23种模式以一种可工程化的公共库而非Example的方式呈现给读者。内容划分为8个部分展开。
图1
“C#面向对象化开发和扩展”是本书讨论的内容基础。图1中不同知识领域的技术层次以其在图中位置的高低表示,知识领域间的学习曲线关系以箭头方向而定(但部分章节内的细节内容可能有交叉)。
另外,XML技术作为SOA、Web 2.0时代的主要数据形式、实体结构、处理逻辑、语义信息、领域语言载体,一直是本书所倡导和推荐的,因此“面向关系数据和XML数据的领域逻辑模式”及最终的“基于XML的应用建模”在“C#面向对象化开发和扩展”之上,贯穿全书模式介绍的始终。
第1篇 预备知识——发掘用C#语言进行面向对象化设计的潜力
第1篇主要对本书进行一些概括性介绍。
第1章 重新研读C#语言
虽然本书不是一本介绍 C#语言的书,但是由于工程中,特别是规模相对较大的工程中常常需要使用一些 C#语言特有的高级特性,这些特性的介绍又要结合面向对象开发基础之上扩展的一些诸如“异步通知机制”、“配置—对象映射机制”、“操作符重载”等特性,所以专门增加了一章,介绍C#语言这类开发的梗概内容。
第2章 开始每个设计模式之前
为了切合本书的副标题,如我们一般项目的开发习惯一样,这一章主要准备一些后面每个模式具体编码中需要使用的公共机制的实现。
第2篇 创建型模式——管理并隔离对象实例的构造过程
创建型模式为了隔离客户程序与具体类型实例化的依赖关系,通过将实例化职责委托他方对象的办法,保证客户程序(或外部系统)在获得期望具体类型实例的同时不必发生直接的引用。
第3章工厂&工厂方法模式
工厂方法的意图非常明确,它把类的实例化过程延迟到子类,将new()的工作交给工厂完成。同时,增加一个抽象的工厂定义,解决一系列具有统一通用工厂方法的实体工厂问题。在.NET 平台中,我们可以借助配置、泛型和委托的方法在实现经典模式目的的同时,获得工厂类型与客户程序间更加松散的构造过程。
第4章 单件模式
使用Singleton 模式的意图就是要保证一个类只有一个实例,同时提供客户程序一个访问它的全局访问点。虽然经典Singleton 模式旨在控制实例的数量,但受到种种“隐性”破坏因素的威胁,我们还需要实施各种针对性的实例数量控制手段。
第5章抽象工厂模式
提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们的具体类型。借助类型映射器、配置、委托等手段都可以进一步扩展抽象工厂构造“一组相关或相互依赖对象接口”的能力。
第6章 创建者模式
创建者模式可以将一个产品的内部表象与产品的生成过程分割开来,从而使一个建造过程生成具有不同的内部表象的产品对象。根据上下文需要,我们除了要设计具有装配能力的创建者,必要的时候还要能够有序拆解目标实例。
第7章 原型模式
用原型实例指定创建对象的种类,并且通过拷贝(克隆)这些原型创建新的对象。克隆过程可“繁”可“简”,而且我们往往希望对于克隆过程实施更深入的控制,为了简化开发,我们还要准备点工具类完成不同的克隆过程。
第3篇 结构型模式——针对变化组织灵活的对象体系
结构型模式的重点在于如何通过灵活的体系组织不同的对象,并在此基础上完成更为复杂的类型(或类型系统),而参与组合的各类型之间始终保持尽量松散的结构关系。
第8章 适配器模式
适配器模式的意图就是通过接口转换,使本来不兼容的接口可以协作。一次适配也许不能满足我们的需要,项目中我们往往需要做适配器间的互联,随着XML数据的广泛使用,我们还需要专门的适配器设计思路。
第9章 桥模式
一直觉得在设计模式中,桥模式(或称之为桥接模式、桥梁模式)的意图表述是最普遍正确但又难于明确化的:“将抽象部分与它的实现部分分离,使它们都可以独立地变化”。通常我们应用中的类型关系会越来越复杂,一座桥也许满足不了我们的需要,那就不妨多搭几座,然后把它们连起来。
第10章 组合模式
组合模式需要解决的主要意图是把具有“整体—部分”关系的对象组织成层次结构,客户程序在使用的时候可以用同一套方法处理单个个体或容器对象。项目中,虽然实体对象和XML对象都具有“整体—部分”关系,但处理的手段却是各异的。
第11章 装饰模式
装饰模式的意图非常明确:动态地为对象增加新的职责。Decorator与Builder的协作可以令对象构造过程更加丰富多彩,在.NET平台上我们还可以用更简单的Attribute方式实现这些过程。
第12章 外观模式
外观模式的意图很明确:为子系统中的一组接口提供一个高层接口,该接口使子系统更易于使用。它的主要动机是减少“子系统”内部与外部间对象通信的依赖复杂程度。远程访问需要好的外观,Web Service访问需要好的外观,就连编码我们也可以面向类型使用者提供好的外观。
第13章 享元模式
享元模式的意图是通过共享有效支持大量细颗粒度对象。管理并有效共享对象实例的使用是中、大型应用需要慎重设计的问题,基于享元模式正好提示我们一个对象池的好思路。
第14章 代理模式
通过一个代理对象,控制其他对象访问目标对象。为了让我们的逻辑更多关注于当前的处理,而把各种复杂的网络调用、数据访问、缓存等透明地提供给客户程序,代理类型需要多种多样的扩展。
第4篇 行为型模式——算法、控制流和通信关系的对象化处理
行为型模式关注于应用运行过程中算法的提供和通信关系的梳理。
第15章 职责链模式
职责链模式用于对目标对象施与一系列操作的情况,为了避免调用双方和操作之间的耦合关系,可以把这些操作组成一个链表,通过遍历链表找到适合处理的操作。链式处理帮助我们把加工过程联动起来,不过有时候这还不够,还需要在我们不直接调用的情况下,把这个联动过程自动化,并覆盖到更大范围之中。
第16章 模板方法模式
模板方法模式是面向对象系统中非常朴实的一种模式,体现出面向对象设计中继承和多态的基本特征。由于设计的需要,我们往往会在初期规划一些较粗颗粒度的算法,而且对参与计算的对象进行抽象,明确算法会使用到哪些方法,每个方法可以提供哪些支持,但此时每个方法本身并没有细化;随着开发过程的展开,我们可能会具体实现每个方法,或者对最初的一些方法进行替换,覆盖上新的内容,这样就在一个相对固定的算法框架下,通过子类(或其他方法)的变化,实现了算法的差异性。本章扩展了类型的模板、方法的模板、程序执行框架的模板。
第17章 解释器模式
项目中我们常常会发现有些问题(业务或技术上的)出现得非常频繁,而且它们也基本上以一些规律性的方式进行变化。对于这类问题,如果编写一个对象类进行处理,随着业务变更,我们将频繁地修改代码、编译、部署。与其反复做这种工作,不如把它们抽象为一个语言,借助解释器在更高层次上以更灵活的方式适应变化。实现解释器的方法多种多样,对于不同的“特定领域语言”我们也能找到一些捷径。
第18章 命令模式
命令模式是对一类对象公共操作的抽象,它们具有相同的方法签名,所以具有类似操作的对象就可以被抽象出来,成为一个抽象的“命令”对象。这样实际操作的调用者就不是和一组对象打交道,它只需要依赖于这个“命令”对象的方法签名,并根据这个操作签名调用相关的方法。把处理抽象为具体的命令对象还不够,要根据不同的应用环境,我们还需要赋予它异步、打包、队列的支持。
第19章 迭代器模式
迭代器模式要求我们从使用者的角度考虑如何设计对外访问内部对象接口。即便我们组织的对象系统内部结构很复杂,但对于客户程序而言最简单的方式莫过于通过for/foreach循环依次遍历,至于遍历过程中的次序、分类筛选等则由目标类型自己封装。而 C#语言中就有现成的迭代器。
第20章 中介者模式
中介者模式是在“依赖倒置”原则基础上进一步扩展出来的,它主要解决 M:N依赖关系。松散协调1:N的通知本来就不容易,为了在Web Service时代“规规矩矩”地完成这个过程,我们还要采用专用的手段。
第21章 备忘录模式
备忘录模式的意图是在保证对象封装性的前提下,获取对象内部的状态,并交给第三方对象保存起来,后续运行中根据需要可以把对象恢复成之前的某个状态。此外,还须妥善地保存备忘信息及定义备忘信息的获取手段。
第22章 观察者模式
观察者模式主要用于1:N的通知发布机制,它希望解决一个对象状态变化时可以及时告知相关依赖对象的问题,令它们也可以做出响应。.NET有内置的观察者机制,我们除了可完成个体对象间的异步通知,还可以针对集合类型、服务接口扩展不同的观察者。
第23章 状态模式
状态模式的意图就是引入独立的状态管理类型,由后者负责在对象类型状态变化的时候相应改变对象的行为,这样从外部看上去对象的执行逻辑就好像被修改了一样。状态模式可以主动更迭,可以与用户进行交互,也可以扩展为复杂的工作流机制。
第24章 策略模式
策略模式的意图很明确:定义一系列算法,把它们封装起来,确保它们可以根据需要相互替换。不过,好策略要实施有方。
第25章 访问者模式
访问者模式的意图描述比较复杂:对于一系列元素,在不改变各元素的前提下增加新的功能(操作)。正所谓“外来的和尚好念经”,当类型间产生双依赖关系的时候,我们就用访问者来解决。
第5篇 小颗粒度基础模式和应用案例——服务于细节的基础性模式
相对前面介绍的创建型、结构型、行为型模式,我们往往在项目中局部还要应用一些非常小的基础模式(或被称为idiom),它们虽然在实现上,很多时候技巧性不如GOF模式,但经常会令我们的代码使用起来更“舒服”,构造的数据结构、算法结构也更加清晰,因此专门选择其中几个贴近.NET类型且项目中常常会用到的“小”模式进行介绍。
第26章 成例
也许有些模式化的处理很“小”,但它们很实用。
第27章 GOF总结及应用案例
完成了GOF23和一系列“小”模式的介绍后,为了加深对模式应用技巧的认识,本部分通过一个案例——数据源无关的通用数据访问机制,介绍如何在项目中具体应用上述模式。虽然很多内容看上去有些做作,但具体项目中您可以根据需要和相关位置的变化进行裁减,而不像这个案例中“为了模式而模式”。
第6篇部分架构模式——面向应用全局的模式化处理
除了上面介绍的GOF,项目中我们还往往会用到一些涉及应用全局的模式化的实现方法。现在已经被普遍应用的N 层模式及某些关键性框架产品采用的“微内核”模式,由于其概念性很强且已被绝大部分开发人员所了解,但实现技巧不一而足,所以并没有被纳入本部分。本章选择部分有代表性的模式,介绍它们的模式意图和具体应用情景,并介绍如何基于.NET和C#实现它们。
第28章 MVC模式
用MVC分解应用中数据、控制和展现的关系。
第29章 管道—过滤器模式
当需要一个线性的连续处理时,我们不妨把消息和数据投入到一个管子里——筛选它。
第30章 出版—预订模式
“预订”是生活中常常遇到的情况,针对不同预订要求、不同执行环境,我们需要灵活设计。
第31章 Broker模式
用Broker梳理M:N个应用的交互。
第32章消息总线模式
面对交织在一起的企业应用环境,我们为它们准备一个总线,需要交互的都通过总线完成。
第7篇部分Web和Web Service模式——面向服务开发中的模式化处理
前面我们的讨论一般都集中在本地应用的范畴内,随着SOA和Web 2.0概念逐步被业界所推崇,Web和Web Service应用也日趋普及。面对新挑战,我们可以用一些专门的模式化方法应对。本部分是一些针对该领域的模式设计技术。
第33章 页面控制器模式
用对象化的方法,把页面中的处理抽象为独立的控制对象。
第34章实现Web服务依赖倒置
要实现服务间的松散耦合,先实现服务接口间的依赖倒置。
第35章 Web服务适配器模式
采用适配器完成服务接口间的适配。
第36章 Web服务数据传输对象模式
采用数据传输对象缓解频繁服务调用中中间信息的缓冲。
第37章 Web服务事件监控器模式
通过增加监控器,为本来无状态的服务调用赋予异步的Observer能力。
第38章 Web服务拦截过滤器模式
通过横切手段完成服务调用过程中外部机制的透明注入。
附录A 面向关系数据和XML数据的领域逻辑模式
在领域驱动设计(DDD:Domain Driven Design)中,实现业务逻辑层主要有三种模式Transaction Script、Domain Module和Table Module。随着业务逻辑复杂程度的增加,采用各模式实现的工作量变化趋势有所不同;根据应用特点不同,三种模式也各有优势。应用建设初期选择的实现模式随着业务需求和历史数据量的变化可能需要进行调整,此时要增加一个适应性机制,保证在尽量不影响客户程序的前提下,选择合适的实现模式。随着XML数据使用日趋广泛,须借助XPath、XQuery和XSL为层次型数据增加专门的扩展机制,使得基于XML数据源的业务逻辑也可以采用上述三种模式实现。
附录B 基于XML的应用建模
介绍如何采用一些XML技术服务应用于开发中的主要领域。
致谢
首先,要感谢工作单位的各位领导、同事及带我进入.NET 开发领域的师傅——沈嵘,在有机会经历这些规模化的.NET项目之后,我才对.NET开发有了一些了解和认识,并且可以在后续项目中不断强化和完善这些认识,进一步总结一些模式化的处置手段。
另外,博文视点的各位老师和编辑也多次给予我无私的帮助,尤其是周筠老师、徐定翔编辑和陈琼编辑。作为我的第一本书,最初的书稿存在很多不足,是博文视点的各位老师和编辑不厌其烦的指导和建议才让本书的内容更加“规矩”。还要感谢李会军、王涛、蒋波涛等朋友对本书的关心和支持,正是他们无私的帮助,才使得我可以纠正并调整书中很多不足之处。
由于笔者工作年限不长,对.NET社区,尤其是.NET开源社区了解有限,所以书中难免存在不足、错误,也请专家、读者不吝指正。