You are on page 1of 35

应用框架的设计与实现—— .

NET 平台

1.1 什么是应用框架

在说服你接受应用框架的思想之前,我们首先需要来定义应用框架是什么。我们从美国传
统词典(American Heritage Dictionary)中对框架(framework)的定义开始吧。“支撑或围住其
他物体的结构,尤指用作建筑物之基础的支撑骨架;一种基本结构,如关于一部作品或一系列
观 点 的 基 本 结 构 ( A structure for supporting or enclosing something else, especially a skeletal
support used as the basis for something being constructed; a fundamental structure, as for a written
work or a system of ideas)。”

术语“框架”对不同的人,含义不同。政治家用这个词描述某些政策和解决问题的某些措施。建
筑师用这个词描述建筑物的骨架或结构。软件架构师用这个词描述有助于软件应用开发的一组
可重用的设计和代码。“框架”的最后这个含义,正是本书其余部分要讨论的。
“结构(structure)”一词着实反映了任何框架的本质。结构无处不在。当开始建造一座新的建

筑物时,你会观察到,它的结构先被搭建。当我写书的时候,我也首先为书中要讨论的内容拟
订一个结构,或者称为提纲。通过搭建一个“结构”,可以迫使我着眼于全局。对建筑来说,着
眼全局将迫使建筑师深入考虑:建筑物的每一部分会如何影响其他每一部分。对写书来说,着
眼全局会迫使我去考虑如何组织每一章和每一个主题,才能使整本书易于读者理解。
结构在应用开发中也扮演了重要角色。一个相当复杂的应用包含了如此之多不断变化的细
枝部分,以致人们无法把握它们的复杂关系。而结构帮助我们将这些不断变化的细枝部分,组
织成易于理解的少数几个主要部分。在开始开发应用时,结构能为我们提供进一步实现的上下
文(context)。应用框架为开发者提供了结构和模板,开发者以此为基线(baseline)来构建他

们的应用系统。这样一个框架通常会包含抽象类(abstract classes)、具体类(concrete classes)

和类之间的预定义交互(predefined interaction)。开发者在框架之上构建应用,通过重用由框

架提供的代码和设计,减小了开发工作量。图 1-1 概括了应用框架和业务应用( business

application)的关系。

当然,许多应用系统的开发没有使用框架,所以,你也许做着开发工作却甚至并不知道框
架的概念。在应用开发领域,无论有没有框架,所有事情照样能做。然而,框架能为应用提供很
多好处,采用应用框架方法对应用开发大有裨益。而推广应用框架的主旨,就是为了获得这些
好处。
图 1-1 应用和应用框架的关系概括

应用框架并不是新概念,各种类型的框架已经被使用大约二十年了。第一个被广泛使用的
框架是模型 -视图控制器( Model-View Controller,MVC),它是一个由施乐( Xerox)公司开

发的 Smalltalk 用户界面框架。这种使用观察者设计模式(Observer design pattern)的 MVC 方法

已经被很多用户界面系统采用。除了 Smalltalk MVC 以外,还涌现出其他一些用户界面框架,

它们对开发运行于不同操作系统之上的应用都起到了辅助作用。著名的用户界面框架有 MacApp

和 MFC,它们分别辅助 Macintosh 和 Windows 操作系统之上的应用开发。


虽然框架概念在用户界面开发中被广泛采用,但是它并不局限于用户界面框架。框架概念
也用于通用应用开发。Taligent 是一家开发面向对象操作系统的公司,它曾提醒软件业界注意框

架概念的潜力。Taligent 公司在 1992 年由 IBM 和 Apple 公司合作创建,目的是开发可以运行在


任何硬件平台之上的新型操作系统。然而,随着时间的推移,业界对这种新型操作系统的兴趣
降低了。于是,Taligent 公司就转向开发基于现有操作系统的框架了。CommonPoint 是 Taligent 公
司开发的一个框架,旨在减小应用开发的工作量,其方法是为开发者提供一个综合编程环境
(comprehensive programming environment),类似于 Sun 公司如今提供的包括 Java 语言和运行

时虚拟机的 Java 环境。


Sun 公司的 Java 环境和 Microsoft 公司的.NET 环境,不仅提供了新型的语言和虚拟机,还

提供了它们自己的框架。在过去几年中,使用 Java 或.NET 的开发者能够充分感觉到这两种框架

为他们的应用开发所带来的好处。Java 和.NET 都是旨在支持所有业务类型的应用系统的通用框


架,因此,它们不能包含任何业务领域相关的类和设计。然而,基于这些通用框架之上的业务
框架,可以为诸如供应链系统和金融应用等特定业务领域提供相关服务和专门支持。
IBM(后来收购了 Taligent)也开发了自己的面向业务领域的框架,被称为 San Francisco

项目。它是用 Java 开发的,由针对不同业务领域的应用框架组成,例如,订单管理、仓库管理、

财 务 会 计 核 算 ( general ledger management ) 等 。 与 Java 和 .NET 这 些 通 用 框 架 不 同 , San

Francisco 框架是专门为特定业务领域而设计的。

使用应用框架有五大优点:模块化( modularity)、可重用性( reusability )、可扩展性

(extensibility)、简单性(simplicity)和可维护性(maintainability)。

1.3.1 模块化

模块化就是把应用分割成多个组件或模块。这样,开发者就可以采用各模块互不影响的方
式使用应用框架——希望使用应用框架某个组件的开发者,不会受到框架其他组件潜在变化的
影响。当开发者基于框架之上构建应用时,他们的开发活动会更好地被隔离开,而不受应用框
架其他部分变化的影响;从而,开发效率显著提高了。把框架划分成模块,我们就可以指派擅
长该部分的开发者来完成它,从而使生产效率最大化。以开发 Web 应用为例,模块化带来如下

好处:指派熟悉表现层的用户界面开发者负责应用的前端( front-end),开发效率会更高;而

指派熟悉业务逻辑层的开发者负责应用的中间层( middle tier)和后端(back-end),开发效率


也会更高。同样,对使用应用框架来说,有的开发者擅长使用框架的用户界面相关模块,而有
的开发者擅长使用框架的业务对象。
1.3.2 可重用性

代码的可重用性是应用开发中最重要和最令人期待的目标。应用框架能为基于其上构建的
应用提供这种可重用性;而且,不仅应用框架的类和代码被重用了,其设计也被重用了。不同
的应用通常会包含很多本质上相似的任务,然而,团队中的不同开发者往往都会自己实现一遍。
这种重复实现,不仅在重复的代码上不必要地浪费了资源,也为以后的维护带来了困难。这是
因为,要保证修改得彻底,必须对应用的多处进行重复修改。另外,由于每个开发者的设计方
法可能不尽相同,这就可能使应用的设计变得很糟糕,为以后带来不可预料的问题。然而,有
了应用框架,我们能将大量重复代码和通用解决方案从应用层移到框架层。这样一来,开发者
编写和维护的重复代码的数量减少了,开发效率也大幅提高了。应用框架是精心设计的组件的
汇集之处,其中不乏“久经考验”的优秀的软件设计方案。开发者并不一定是软件设计专家,
但是一旦开始使用框架组件来构建应用,他们就自然而然地在重用优秀的软件设计方法了,比
如藏在框架组件背后的设计模式等等。
1.3.3 可扩展性

可扩展性是往现有的框架中增加自定义功能的能力,它使开发者不仅能够“即拆即用
( out of the box )”地使用框架组件,还能够改变组件,以适应特定业务场景( business

scenario)的需要。可扩展性是框架的重要特征。每一个业务应用都有独一无二的业务需求、架构

(architecture)和实现。虽然框架本身容纳所有这些具体情况是不可能的,但是,框架采取了支

持客户化(customization)的设计思路;这样,不同的业务应用依然能够使用框架的通用功能,
同时,开发者通过在框架中插入自定义的业务逻辑,可以自由地按照独一无二的业务需求剪裁
他们的应用。框架本身有了很高的可扩展性,就能适应更多类型的业务应用。然而,在创建框架
时,其可扩展性总应根据它将支持的应用的设想和上下文来决定。框架的可扩展性越高,使用
框架的开发者需要编写的代码就越多,他们需要了解的框架机制细节也越多,这样就会降低开
发效率。一个高度可扩展框架的极端例子是 Microsoft .NET 框架,它所支持的应用范围非常广

泛。的确,使用.NET 框架开发应用几乎没有限制,但结果,你也失去了应用框架能够提供的好
处。关键在于,找出你要开发的特定应用中最可能发生变化之处,在那里增加灵活性
(flexibility)和可扩展性支持。

1.3.4 简单性

术语“简单性(simplicity)”的含义不仅仅是“简单”。简单性指的是一种方法:框架通
过封装处理流程的控制逻辑,使它对开发者透明,来简化开发工作。这种封装也是框架和类库
(class library)的区别之一。类库由许多现成的、供开发者用于构建应用的组件组成,但是,开
发者必须理解不同组件之间的关系,并编写处理流程代码把众多组件组织起来。框架则不同,
它通过预先把众多组件组织在一起的方式,封装了处理流程的控制逻辑;因此,开发者就不用
再编写控制逻辑来组织组件之间的交互了。图 1-2 说明了类库和框架的这种区别。
图 1-2 类库和应用框架的比较
由图可知,应用开发者使用类库这种方法时,必须编写管理类库中不同组件实例
(instance)的控制流程。为此,应用开发者必须充分理解每个相关组件,以及组织组件协作所
必需的业务逻辑。而使用框架这种方法时,由于大部分处理流程已经被框架管理了起来,所以
开发者需要编写的控制代码就非常少。由于应用框架隐藏了不同组件之间的处理流程,这就免
去了开发者编写协调逻辑( coordination logic)之苦,也不用经历编写这些协调代码的学习曲
线了。既然处理流程的控制逻辑从应用层移到了应用框架层,那么框架的设计人员就要运用其
架构和领域知识,来定义框架内的组件该如何协作;而使用框架的开发者,几乎无须知道框架
组件如何协作,就能高效地开发应用。
1.3.5 可维护性

可维护性是业务需求改变之后,其应用便于修改的能力。它是代码重用带来的一个受欢迎
的效用。框架组件通常会被多个应用或单个应用中的不同部分共享。只保留一份框架代码库
(code base)使得应用易于维护,因为当需求改变时,你只须改变一次。应用框架也可能包含

多个层(layer),每一层关于应用要支持的业务都有某些假设(assumption)。其中,最底层包
含了没有任何业务假设的框架组件,它们是框架中最通用的组件;层次越往上,其组件依赖的
业务假设(business assumption)越多,因此也对业务需求和业务规则的变化越敏感。每当变化
发生时,只有那些业务假设被打破的层中的组件需要被修改和测试。因此,让框架的不同层包
含不同级别的业务知识,能够减少因改变业务需求和业务规则所带来的连锁反应。这也使得维
护成本降低,因为只须维护因业务规则改变所影响到的代码。更多有关框架分层的知识,我们
将在第 2 章讨论。
你已经了解了框架在支持应用开发方面的出色表现,但尽管应用框架在很多情况下都是
最佳之选,这并不意味着选择应用框架总是正确的。在探索应用框架潜力的同时,我们一定也
不要忽视了开发应用系统的目的。关于开发和使用应用框架,我们需要考虑两件事情以判断应
用框架是否有助于我们达到目的,那就是框架开发和用户培训。
1.4.1 框架开发
开发应用框架并非易事,也不廉价。为了开发出高度可用和可扩展的框架,你需要首先找
到合适的人选——他们不但是业务领域的专家,还是软件设计和开发的专家。上述两项技能缺
一不可:没有业务知识,就无法开发特定业务领域框架层(business-domain-specific framework

layer),应用开发人员要依靠该层弥补他们业务领域知识的欠缺;没有软件开发技术知识,就

无法将框架思想贯彻到具体的框架代码中去,会导致应用开发人员无法重用和扩展这些代码。
开发高质量框架,找到业务领域和软件开发的双料专家是第一关。
无疑,框架的设计和实现需要大量人力资源。开发应用框架要求的技能不同于开发应用。
框架的设计者必须决定:应用开发者如何从框架提供的服务和架构中获益;如何去抽象应用中
特定的通用逻辑,以使开发者通过框架重用这些逻辑;如何在框架的恰当处提供扩展点( hot

spot),以使开发者能插入代码获得特定结果。开发框架的一些工作是很抽象的,并且严重依赖

于一个前提:应用开发者将如何使用框架构建应用。一开始就把所有问题都搞清楚是很困难的,
因为在设计时只能猜测:最终应用是什么样子,又是如何被构建以解决业务问题的?有的框架
组件,可能最初被认为将被应用的多个部分共享,但最终发现并非如此。有的代码段,可能最
初被认为是某场景( scenario)独有的,确实应该在应用层实现它,但最终发现它包含通用的
抽象,应当被加入到框架层,以便被整个应用所共享。在已经开始使用框架之后,新的业务需
求或变更了的需求可能导致两种情况:新的组件加入框架,或者现存框架组件改变。你可能想
到了,为了使框架适应将在其上开发的应用的要求,框架开发过程要经过一系列迭代周期
(iteration)。框架的开发生命周期和常规应用的相似,即反复地进行分析、设计、实现和稳定工
作。框架开发是非常典型的进化型工作,要求持续开发和相关支持工作,以保证框架的实用性。
1.4.2 用户培训

因为框架的用户是构建应用的开发者,所以其用户培训就是训练开发者使用应用框架。为
了利用框架,开发者必须有足够的应用框架知识,以及如何使用框架进行开发的知识。但是,
学习框架是一个耗时的过程。有几个因素导致了学习应用框架的困难。
应用框架天生就是不完整的应用,其中缺少的部分需要开发者编写应用层代码来补充。然
而,在应用开发完成以前,对许多开发者来说,框架本身可能不太容易理解,因为框架的每一
部分与其他部分的交互关系,并非自始至终都很明显。
框架中也包含许多控制整个框架处理流程所必需的总控代码。尽管框架的目的之一就是把
这些复杂的控制逻辑对开发者隐藏起来,但是在培训时,开发者还是应当理解框架是如何工作
的。因为框架中的很多控制逻辑以及类之间的依赖关系,并非直截了当,而且比较复杂,所以
理解框架的工作原理并非易事。应用框架提供了新的编程模型,它包含了很多开发者陌生的
API、服务和配置设置项。开发者需要花些功夫学习应用框架,才能流畅使用它,最终高效地在

其上开发应用。
尽管学习曲线是要经历的,但我们可以通过完善的文档,以及演示如何在各种场景使用
框架的范例,来加快开发者掌握开发应用框架的速度。对于学习编程来说,一个范例,抵得上
一大堆文字和图片。通过考虑潜在的开发成本和工作量,我们能够权衡出是否需要应用框架。并
非所有应用都需要在应用框架之上构建,很多成功的应用并未使用应用框架。有这种情况:你
希望投入有限的费用并快速开发出解决方案,这时可能使用框架节约的成本还没有开发框架投
入的成本多。相反,也有这种情况:应用框架被多个应用共享,从而总的开发成本显著减少。还
有这种情况:你希望今天在应用框架上投资,以为将来的开发提供一个可扩展和可重用的基础
平台。总之,就是看应用框架是否有助于你达到项目的预期目标。开发应用框架就像在股市投资
好的投资应该对你的投资目标有利,而不是看它们今天是否赚钱。
在应用框架介绍这一章中,我们了解了应用框架的定义和应用框架的简要历史。之后,我
们讨论了如何在应用开发中有效地使用框架,以减小总的开发工作量。然后,我们讨论了以应
用框架作为起点来开发业务应用的好处:模块化、可重用性、可扩展性、简单性和可维护性。我们
还讨论了开发和使用框架的代价,例如额外的开发工作量和开发者培训问题。知道了开发应用
框架的好处和代价,有助于我们做出正确的选择——在具体情况下是否应当使用框架。在下一
章,我们将讨论框架的内部组成,以及如何使用面向对象技术来开发既可重用又可扩展的应用
框架。

2.1 框架的分层

在第 1 章中,我们已经认识到,应用框架是个应用“半成品”,它能作为一个业务应用
的起点。基于框架开发的应用由两层组成:应用层和框架层。框架层可能又包含众多组件,这些
组件可以被划分为两类:特定领域组件和跨领域组件。图 2-1 显示了应用的不同组成部分以及它
们之间的关系。

图 2-1 应用中的多个层
接下来,我们简要介绍每一层,以及它们在整个系统中扮演什么角色。
2.1.1 业务应用(Business Application)层

业务应用层代表客户化应用(custom application),它由应用开发者负责开发。为了达到
待开发的特定应用目的,业务应用层必须实现事无巨细的业务知识。开发人员根据业务分析员
描述的详细场景,来构建业务应用层。当业务逻辑和规则发生变化的时候,最可能发生变化的
正是业务应用层,特别是当这种变化比较小并已被隔离的时候,更是如此。
2.1.2 应用框架(Application Framework)层

应用框架是应用的半成品,软件架构师(architect)开发它,作为应用开发者构建业务应
用层的基础。应用框架层可以被进一步划分为两层:特定领域框架层和跨领域框架层。
特定领域框架(Domain-Specific Framework)层
特定领域框架层由针对特定业务领域的专有组件组成。相对业务应用层而言,特定领域框
架层实现的知识,是对特定业务领域的所有应用通用的。而业务应用层的业务知识和业务逻辑,
则是专门针对某个特殊应用的。
你可以把特定领域框架层想象成一个国家的宪法,而把业务应用层想象成某个州或地方
政府的法律。宪法并不记述各个州必须制定的特有法律,而是记述一些原则,整个法律系统是
在这些原则的指导之下而制定完成的。每个州都可以制定自己的法律,但是所有这些法律都必
须以宪法规定的原则为基础。也就是说,只要和宪法保持一致,一个州就可以自由制定最适合
该州情况的法律。和宪法类似,特定领域框架层并不规定每个业务应用该怎么构建;相反,它
提供一组组件,这些组件封装了特定业务领域的核心业务特征和核心业务过程。例如,对于一
个在线 B2C 业务领域而言,购物车组件就是特定领域框架的组件,该组件负责描述客户选购的
商品项、每项的数量和选购时间。不同的业务应用(这里仅指在线购物网站)可以在不同的场景
中以不同的方式来使用这个购物车组件,但它们都有个共同特点:都需要一个对象提供诸如客
户选购的商品项、每项的数量和选购时间这些信息。
对业务应用层来说,开发人员负责设计和实现实际应用;特定领域框架则不同,它的设
计和实现人员,必须对特定业务领域有专门的知识和深入的理解,并知道如何封装和抽象业务
领域知识,才更易于实际业务应用层的开发。尽管开发特定领域框架层的时候,软件架构设计
技能是很重要的,但业务的专门知识对该层的成功尤为关键。
特定领域框架层所包含的业务领域知识,比业务应用层的业务知识稳定多。我们期望,当
业务应用层的业务规则发生变化时,特定领域框架层能够很少变化或者不变化。
跨领域框架(Cross-Domain Framework)层
跨领域框架层由不包含业务领域知识的框架组件组成。因为不包含业务领域知识,所以跨
领域框架层能够被多个不同业务领域的应用共享。换言之,跨领域框架层中的组件和服务通用
于大多数的应用,而不论该应用是针对哪个业务领域的。
不同业务领域的应用有很多公共主题,这些公共主题可以被“打包”成跨领域框架层。例
如,每一个应用都需要某种管理配置信息的功能,这些配置信息必须能被应用的不同部分所使
用。如果已经有了这样的一个配置服务和架构,则众多不同业务领域的应用就不用再投入精力
去重复开发了。再例如,在分布式应用环境下,一个应用常常需要和另一个系统中的应用通信。
所以,一个现成可用的、在不同应用之间传递信息的事件通知服务,将对开发分布式应用大有
好处。所以不难明白,如果能在不同应用中识别出这些公共主题,并进一步开发出相关的服务
和组件去满足这些公共需求,我们就能显著提高代码和设计的重用性。跨领域框架层的开发者
应当具备三个条件:他们曾开发过许多应用,他们应当对软件设计有深入的理解,并且,他们
应当知道哪些是众多应用中的通用功能。他们不必太了解特定业务,但他们必须有优秀的面向
对象技能,只有这样,他们开发出的框架,才能让应用层的开发人员很容易地插入业务逻辑,
以解决特定应用的问题。
既然跨领域框架层并不包含特定的业务领域知识,就可以把它看作大部分应用所通用的。
因此,改变业务规则和业务需求,并不会影响跨领域框架层。然而,在开发过程中,如果认定
出现了新的公共主题,跨领域框架层则将会受到影响,因为这个公共主题要在跨领域框架层中
被实现。另外,如果最终框架设计中的某些方面影响了业务应用层与之相适应,跨领域框架层
也必须被修改。
2.1.3 基础框架(Foundation Framework)层

基础框架是一种编程模型(programming model),应用框架和业务应用在其上被构建。这

一层是由软件供应商开发的。最负盛名的基础框架有 Sun 的 Java 环境和 Microsoft 的.NET 框架


等。基础框架被用来开发五花八门的应用,它并不包含业务领域知识。对基础框架的更改,主要
是出于高性能或者支持新技术的需要。
2.1.4 操作系统层

义如其名,操作系统层代表操作系统这一层次。它除了提供对诸如 CPU、存储器和磁盘这
些系统资源的访问之外,还提供对位于其上所有层的访问。
现在,我们对整个应用系统中的不同层,以及各个层在应用系统中的位置,都有了基本
的理解。接下来,让我们看看如何实际构建一个应用框架。首先,我们来讨论应用框架的开发过
程。
在明确了需要应用框架之后,应当首先确定框架开发过程包括哪些主要阶段。图 2-2 显示
了这四个主要阶段:分析、设计、实现和稳定。

图 2-2 框架开发过程
灰色的内圈代表阶段,外圈代表每个阶段中的主要任务。让我们看一看每个阶段之中包括
了什么。
2.2.1 分析

要开发应用框架,第一个阶段是分析阶段。和应用开发一样,框架开发首先要确定框架的
范围(scope)和目标(objective)。
这个框架的主要功能(feature)是什么?
这个框架将要支持什么类型的业务应用?
这个框架要支持哪些用例(use case)?
开发者如何在这个框架的基础上开发他们的业务应用?
既然框架是为了支持业务应用开发的,那么这个框架能够支持哪些业务领域?
框架开发的分析阶段,为了确定框架的范围和目标,我们要回答上述这些问题。
在分析阶段,我们还要制订出逐步改进框架的迭代计划(iteration plan)。你不要期望在

第一个迭代周期( iteration)就彻底搞清楚每一件事,因为框架的开发会涉及很多复杂而又抽
象的任务。当开始实现这个框架的时候,你也许会发现,需要删去某些东西不做,还需要增加
一些东西;你也许还会修改框架的某些部分,以使应用开发者能更高效地使用它。当开发者开
始使用这个框架之后,迭代计划里应当包含搜集改进( enhancement)和修改( fix)意见的工
作,这些建议将作为下一个迭代周期的输入。除了上述迭代计划,你还应起草项目计划,并确
定每个阶段主要里程碑的时间和文档。
2.2.2 设计

确定了应用框架的范围和目标之后,下一阶段就是设计阶段。该阶段有两大任务。首先,
对 特 定 领 域 框架 层和 跨领 域框 架层 , 都 要识 别出 通用 点( common spot )和 扩展 点 ( hot

spot);其次,为框架设计架构(architecture),它将用作实现阶段的蓝图。

通用点和扩展点是框架开发的专有术语,本章后面部分会更详细地讨论这两个概念。简要
而言,通用点是框架中不大变化的部分。通用点作为框架组件或服务,应用开发人员通常无须
做太多定制就可以使用它们。而扩展点,是框架中变化频繁的部分。使用框架的时候,开发者必
须在扩展点处提供特定的业务逻辑。扩展点被实现为抽象方法(abstract method),这样,就要
求开发者实现特定业务逻辑。识别出了业务应用中的通用点和扩展点后,你就能够确定框架中
的具体组件和服务。在业务领域中识别易变部分和不易变部分并非易事。为了设计出既易用又可
扩展的框架,业务专家和软件架构师需要协同工作,找出框架各个层中的通用点和扩展点。在
业务专家和软件架构师列出了待开发的组件和服务,并区分出了哪些是通用点哪些是扩展点之
后,软件架构师就可以开始设计框架的架构了。其间,软件架构师应当提交一些设计级的可提
交工作产品,比如类图和活动图,它们在实现阶段将被使用。软件架构师还应考虑与组件和服
务设计相关的技术,比如设计模式,以使最终的框架代码具有更好的可重用性和易扩展性。
在设计阶段,也可以创建应用框架原型( prototype ),然后在其上构建一个样本应用;
通过该样本应用测试应用框架原型,有助于你了解所开发的框架如何用于构建业务应用,并洞
察到框架设计中潜在的可改进之处。
2.2.3 实现

在框架设计阶段之后,是框架的编码实现阶段。实现阶段的目的是,在规定的时间内,开
发一个满足需求的框架。如果你的框架设计工作做得很不错,那么实现起来和常规的应用开发
没有什么不同。开发应用时,不同的团队成员可以在同一个应用的不同部分并行工作;同样,
只要应用框架被合理地划分成不同模块,我们也能够在应用框架的不同模块中进行并行开发。
在框架开发期间,对新实现的功能进行单元测试(unit testing)是件棘手的事。对应用开发而言,
开发者只需简单地测试新实现的功能的用例,看看它是否按照所期望的那样工作就可以了。然
而,框架的单元测试远没有业务应用的单元测试那么直观,因为此时,使用框架的应用甚至还
没有创建,所有应用需要的业务数据也都没有产生。
和应用开发一样,在框架实现阶段也会不断产生里程碑发布(milestone release)。你可以

采用多个增量发布( incremental release)的方式,这样,你就可以先把应用框架的一部分提供


给开发人员,从中得到关于潜在改进和修改的反馈,以用于指导下一个发布。
2.2.4 稳定

稳定阶段是一个迭代周期的最后一个阶段,它的工作集中在测试、修改 Bug、开发者反馈、
写文档和进行培训。
由于框架的主要使用者是开发者,所以框架测试通常是由开发者驱动的,而不是由专门
负责质量保证的测试人员或最终的业务用户驱动。在框架开发的稳定阶段,实际应用的设计和
实现工作还没有开始,所以,它的开发者还不会编写太多应用代码。在这种情况下,你要为每
一个框架的组件和服务确定至少一个业务应用的使用场景( usage scenario),并要求应用开发
团队在此使用场景基础上编写一小部分应用程序。每一个框架使用场景都想测试到是不可能的,
因为相关应用代码还没有编写;尽管如此,通过有选择地实现部分业务应用中有代表性的框架
使用模式(usage pattern),还是可以有效地测试你的框架。
当然,开发者想要基于框架开发部分应用,他们必须知道如何高效地使用框架。作为应用
框架的创建者,你应当在稳定阶段就开始培训开发者。你的培训工作做得越好,开发者就越能
有效地测试你的框架,他们将框架广泛用于应用开发工作时,生产率也越高。
除了就应用框架的使用进行频繁的培训以外,书写完善的框架文档也是有必要的,这些
文档对开发者学习使用框架非常有价值。
典型的框架文档包括以下四部分:

框架概览。说明框架的用途,以及框架中提供的主要组件和服务。
有关框架的一些图片、图解和描述。帮助开发者领会框架及其设计思想。
框架功能的 API 参考。使开发者能够在开发过程中随时查找框架的功能。
说明如何使用框架的一组例子。实际例子是说明应用框架使用场景的最佳手段,有助于缩
短学习过程。
在稳定阶段,框架设计者为框架开发的下一个迭代周期,频繁地搜集开发者关于框架的
反馈。当开发者开始使用框架后,框架设计者必须通过为开发团队提供帮助的方式,参与到应
用开发工作中去,为他们回答和解决框架使用方面的问题。
框架的开发过程讨论完了,让我们来看一些开发框架的通用技术和方法,我们将在本书
的其余部分用到它们。
2.3 框架开发技术

为了开发有效的应用框架,你需要了解一些框架开发的通用技术。下面列出了一些有用的
技术和方法,能够帮助你开发出既易用又可扩展的框架:

通用点(Common spots)。

扩展点(Hot spots)。

黑盒框架(Black-box framework)。

白盒框架(White-box framework)。

灰盒框架(Gray-box framework)。

设计模式(Design pattern)。
其中,通用点、扩展点和设计模式是用于开发框架的技术,黑盒、白盒和灰盒是开发框架
的方法。
2.3.1 通用点

义如其名,通用点代表业务应用中反复出现的通用主题的位置。如果应用的某些部分重复
出现,且其中又没有太多变化,那么我们就能将它从应用层中提取出来,作为通用点,封装成
框架层的组件。通过把通用点移到框架层,避免了这些通用点在应用层的重复出现,从而促进
了代码重用。这样,开发者能够简单地在他们的应用中引用框架中的通用点,从而减小了开发
工作量。图 2-3 说明了通用点和框架以及业务应用的关系。
图 2-3 通用点

在图 2-3 中,应用框架中包含了一些组件,这些组件实现了应用中一些不同的通用点。当
开发者开始构建应用时,他们将引用框架组件实现的通用点,而不用亲自去开发它们。结果,
他们要编写的代码总量就减少了。
什么样的主题有资格成为通用点呢?是不是必须在整个应用中,都以精确相同的方式出
现的主题才有资格成为通用点呢?答案是否定的。只要差异不大,你依然可以把这个主题作为
通用点;只是,需要通过参数化或配置设置项的方法,来处理这些小的变化。
将通用点主题移到框架组件中的实际工作并不困难,困难的是在分析阶段,如何从尚未
开发的业务应用中识别出那些通用点。识别这些深藏在应用中的通用主题确实不易,通常要多
次尝试才能做到。
通用点既可以存在于特定领域框架层中,也可以存在于跨领域框架层中。例如,商业文档
交换( exchange of business documents )是 B2B ( business-to-business )应用的重要主题,可以
考虑把商业文档对象作为通用主题,放入特定领域框架层的组件中。另一个例子是数据加密服
务。对很多类型的应用来说,数据的加密解密常常要被应用的不同部分使用,可以考虑把数据
加密解密组件作为跨领域框架层组件,它简化了相关应用开发者的工作,减少了代码总量。
从技术观点来看,通用点的实现是直观易懂的。识别出通用点之后,框架设计者可以开发
组件来封装通用点中的主题和逻辑;这些组件常常采用具体类或可执行(executable)的形式。
为了包容组件中那些小的变化,可能需要为该组件引入一些配置数据。例如,你可以在配置文
件中分配特定的段来参数化该框架组件。这样,若要在应用中引入该组件,开发者几乎不需要
写代码就可以办到。
通用点捕捉应用中反复出现的主题。然而,每个业务应用都有其独特性;多个应用之间,
既存在大量通用点,又会因每个应用的本质不同,而存在大量显著不同之处。因此,应用中可
能有通用主题,也有显著的变化( variation)和客户化(customization)。为了保证在上述两种
情况下,应用开发者都能利用框架,你作为框架设计者,也需要考虑这些变化的点。这把我们
带到下一个主题:扩展点。
2.3.2 扩展点

扩展点是应用框架中具有灵活性的点。另一个看待扩展点的方法是,把它们看作嵌入框架
的占位符(placeholder),将来在此处可以针对特定应用进行定制。
扩展点和通用点正好相反。在通用点中,通用主题在框架组件中得以具体实现;而在扩展
点中,框架除安置了一个空的占位符之外什么也没有实现,这个占位符以后会被基于框架之上
构建的业务应用填上客户化的实现。因为每个业务应用负责为框架中的扩展点提供它们自己的
实现,所以框架在每个业务应用中的行为并不相同。这就是为什么尽管这些业务应用之间有显
著差异,但框架还能够被设计成适应不同业务应用的原因。图 2-4 说明了应用框架如何通过扩展
点具有了灵活性。
图 2-4 扩展点

在图 2-4 中,应用框架层中的组件包含了扩展点,或者说供定制的空占位符。每个应用可
能使用很多框架组件;当应用使用的框架组件包含扩展点的时候,该应用需要提供扩展点的实
现。图中,代表扩展点的形状被涂上了不同的花色和阴影,以表示不同应用的不同实现。正如你
所看到的,通过在扩展点内实现不同的逻辑,每个应用从框架组件获得了不同的行为。
和通用点类似,识别扩展点或许也不容易。为了识别潜在的扩展点,你必须彻底理解相关
业务领域,理解业务应用的哪些点可能需要定制。如果框架内有太多不必要的扩展点,将导致
应用开发团队额外的编码工作量;如果扩展点太少,框架的灵活性就差,使得应用开发团队期
望的定制变得困难,于是他们的工作也变得更加困难。尽管通过覆盖(override)基类中大部分

甚至全部虚方法(virtual method)来获得这种可定制性是有可能的,但这么做会降低代码的可
重用性,并影响继承的效果。在框架中创建扩展点并不像创建通用点那么直观易懂,其方法有
二:继承法(inheritance approach)和组合法(composition approach)。先来看看继承法。

继承法(Inheritance Approach)

继承法依靠两个重要的面向对象概念:钩子方法( hook method )和模板方法( template

method)。

钩子方法
钩子方法是一个占位符,供特定应用逻辑来填充。它是扩展点概念在实际类中的表现形式
在很多出版物里,术语“钩子方法”和“扩展点”可以互换。然而,虽然方法( method)是扩
展点最普通的形式,但扩展点并不一定是一个方法,它也可能是一个类或应用程序。尽管钩子
方法也可以出现在具体类中(这一点一会儿在本节就会看到),但通常它还是以抽象类中抽象
方法的形式出现。
从定义来讲,抽象类是包含抽象方法的类。抽象类中的抽象方法不包含方法的实现;要使
用该抽象类,必须从它继承一个类,并在该类中实现抽象类的抽象方法。由于上述特点,派生
类(derived class)有机会引入客户化的逻辑,以使自己的行为符合特定业务需求的要求。下面
的例子说明了抽象方法是如何工作的:

BasicBusiness 类 是 一 个 抽 象 类 , 包 含 三 个 方 法 : ReportTax 、 CalculateStateTax 和

CalculateFedTax,其中后两个方法是抽象方法。BasicBusiness 类用于向州和联邦政府报告所得税,

它包含了虚构的税务申报的业务领域知识。如果账户余额足够,将给政府发一张支票;否则,
账户持有人将申请破产。ReportTax 方法依靠两项信息来决定是缴税还是申报破产:缴纳给州政
府的税款总额和缴纳给联邦政府的税款总额。税法规定了如何计算税款总额,例如,根据交易
的地点和类型不同,将使用不同的税级。 BasicBusiness 组件的创建者并不知道税法信息。

BasicBusiness 类也不知道如何计算税款总额,而是把这个任务交给知道如何计算的类执行:两
个抽象方法扮演了占位符的角色,它们以后会被填上计算税款总额的代码;正如你在上面例子
中所看到的,CheckBankBalance 方法使用这两个抽象方法的返回值作为参数,尽管这两个抽象
方法还没有被实现。
当然,必须有代码真正实现 CalculateStateTax 和 CalculateFedTax 这两个抽象方法,否则

ReportTax 方法就不能提供有意义的功能。实际上,因为 BasicBusiness 类包含抽象方法,直接实

例化它是不允许的,你需要创建一个派生( derive)自 BasicBusiness 的具体类,并实现它的两

个抽象方法。下面的例子显示了一个类,它继承( extend)了 BasicBusiness 并实现了两个抽象


方法:

NewYorkBusiness 是一个具体类,它为纽约州提供了税款计算方法。有了 NewYorkBusiness

类中的客户化实现,BasicBusiness 类中定义的 ReportTax 方法就能够执行有意义的行为了,如


下例所示:

抽象方法对应用开发和框架开发都是非常重要的概念。假设 BasicBusiness 类是框架组件,


而 NewYorkBusiness 类 是 应 用 组 件 , BasicBusiness 类 为 应 用 留 下 了 待 填 充 的 扩 展 点

(CalculateStateTax 和 CalculateFedTax 两个方法)。每个应用可以根据自己的特定需求,来填充


这些扩展点。尽管应用提供抽象方法的实现,但通常它并不直接调用抽象方法——抽象方法通
常通过模板方法来调用。
模板方法
模板方法是 GOF 设计模式之一,它描述了特定操作序列的骨架( skeleton )或处理流程

(process flow),而不是规定每个操作如何执行。在 BasicBusiness 组件的例子中,ReportTax 就


是一个模板方法,该方法描述了报税时涉及到哪些步骤,但没有描述每一步如何进行,因为它
调用的有些方法还没有实现。模板方法强调的是不同对象及方法之间如何协作。在框架开发中,
模板方法包含了业务领域知识,这些业务领域知识规定了不同方法应当如何协作;而抽象方法
为模板方法调用的方法提供了客户化实现的手段。了解模板方法和钩子方法所包含的不同概念
是很重要的;图 2-5 说明了框架中抽象方法(或钩子方法)与模板方法的关系。

图 2-5 中,左边是框架组件,其中有一个已经实现了的模板方法,模板方法中包含了特定
业务领域的专门知识。左边的框架组件还有多个扩展点,它们以抽象方法的形式出现。图中从模
板方法指向抽象方法的箭头,表示模板方法调用多个抽象方法,以实现特定应用的行为。
图 2-5 中,右边是应用组件,它继承了框架组件,并用特定应用的业务逻辑和知识实现了
其中的抽象方法。因为应用组件继承了框架组件的功能,并覆盖了抽象方法(即钩子方法),
所以,该覆盖的方法和框架组件中的模板方法共同协作,将业务领域知识和应用级业务逻辑结
合在一起。
要支持扩展点,不仅可以通过上面讨论的钩子方法和模板方法,还可以通过组合法中的
可插入对象(pluggable object)。
注释:有多篇技术论文描述了这种框架方法,它们采用的术语是“好莱坞原则
(Hollywood Principle)” , 或 “ 不 要 调 用 我 , 我 会 调 用 你 原 则 (don’t call me, I’ll call you

principle)”。该术语描述了这样一个特征:框架中的模板方法调用应用代码,而不是应用代码调

用框架。它们引用这个术语是为了强调框架中的模板方法调用了在应用中实现的抽象方法,并
控制其处理流程;然而,该术语稍微有些误导成分,因为大多数情况下,应用总是要调用框架
的,尽管看上去是模板方法在调用应用代码,但其实是应用代码首先调用了模板方法。
组合法(Composition Approach)
继承法是支持框架扩展点的简单方法。但是,开发者要实现抽象方法,必须知道父类中有
哪些可用的数据和方法,以及它们的相互关系,因此,开发者需要详细了解框架才能使用它。
例如,在 NewYorkBusiness 类中,实现 CalculateStateTax 和 CalculateFedTax 这两个方法,

看上去很简单,但却要求开发者必须知道,有一个名为 income 的保护型浮点变量( protected

float variable),且必须在调用任何一个 CalculateXxxTax 方法之前为该变量赋值。而且,父类向

子类暴露其内部细节,降低了父类的封装度,这可能会导致开发者随意对类的内部状态做出超
越其父类设计者意图的访问和修改。下面是一段用继承法实现扩展点的代码:

就像你所能想象到的一样,随着方法变得越来越复杂,开发者也许需要引用父类内更多
的数据和方法,并且需要了解:通过设置这些数据的值和调用这些方法而改变对象的整个状态
后,会有什么样的后果。
开发者必须了解父类细节信息这一要求,延长了框架的学习曲线,加重了使用框架的负
担。为了让开发者不必知道框架组件的内部细节,有一个办法,那就是将扩展点定义为一组接
口(interface),让这组接口和框架进行妥善的交互。开发者通过实现接口来创建组件,然后把
该组件插入到框架中以定制它的行为。
可插入组件(Pluggable Component)
为了通过可插入组件来支持扩展点,应用框架必须首先为扩展点定义接口。接口描述了要
与之兼容的类必须实现的一组方法。接口仅描述方法的外部特征,比如方法名、参数个数和参数
类型,它并不描述方法如何实现。下面是用 C#语言定义的一个接口的例子:

然后,你通过创建一个具体类并实现接口中定义的每个方法来支持 ICalculateTax 接口,

就像下面显示的 NewYorkBusiness 类一样:

NewYorkBusiness 类实现了 ICalculateTax 接口,它现在已经兼容于,或者说可插入到框架


中与 ICalculateTax 接口协作的扩展点了。借助接口的帮助,应用开发者可以通过把可插入应用

组件加载到框架的扩展点,在框架中安排客户化的行为。图 2-6 说明了组合法如何支持应用框架


扩展点。

使用组合法支持扩展点,开发者需要创建可插入的应用组件,该组件的接口要和框架扩
展点匹配。通过把应用组件和框架组件绑定在一起,开发者能够将组件插入到扩展点。用组合法
来支持扩展点,还要借助另一个称为策略模式(strategy)的 GOF 设计模式。在第 5 章,你将详
细了解策略模式。
现在让我们修改一下纳税的例子,用组合法来支持扩展点。首先,我们需要修改
BasicBusiness 组件:我们这次不把它写成抽象类了,而是写成具体类。下面的代码片断显示了

新的 BasicBusiness 组件:
ReportTax 方法现在有了一个 ICalculateTax 类型的输入参数,这个输入参数提供了客户化

税款计算机制,它就是 BasicBusiness 框架组件中的扩展点。正如你知道的,通过插入客户化应


用组件,我们就可以将特定应用的业务逻辑和知识“填充”到扩展点中去。下面的例子显示了
我们如何编写应用代码,来把应用组件插入到框架中去:

在上面这个例子中,两个 ReportTax 方法将产生不同的结果,因为在两次调用中,框架组

件绑定了不同的 ICalculateTax 组件。


为了让框架组件使用可插入对象,你要么使用上例中的(显式创建然后作为参数传递的 )
方法,要么把应用组件的类型信息( type information)保存到配置文件中,然后通过反射机制

(reflection)创建适当组件,并动态插入到框架组件中。
识别通用点和扩展点,并实现它们,是应用框架开发的中心任务。根据你支持通用点和扩
展点所采用的方式不同,你可以创建白盒框架、黑盒框架,或者灰盒框架。
2.3.3 白盒框架

白盒框架是由抽象类组成的框架。开发者使用白盒框架时,要继承框架中的抽象类来建立
一个具体类。白盒框架使用继承法支持扩展点。图 2-7 显示了一个白盒框架。

白盒框架相对容易开发。你可以先参考你以前开发过的类似应用,从中提取出抽象类,然
后识别它们的扩展点,并把扩展点实现成抽象方法。开发白盒框架时,使用模板方法为每一个
框架组件中处理流程的模式给出一个假设,这些假设常常建立在业务领域的专门知识和以前开
发业务应用的经验之上。当开发者开始使用白盒框架时,它们只需在派生类中覆盖并实现为数
不多的抽象方法,而不需担心整个处理流程以及抽象方法在框架内部如何被调用。这使得开发
者可以把精力完全放在抽象方法上,而无须担心这些方法和框架的其他部分如何交互。
当然,几乎每件事都需权衡——白盒框架易于设计和实现,但它也有缺点。首先,它缺乏
灵活性。当你通过模板方法决定框架组件中的处理流程时,实际上你是在组件中对处理流程和
协调逻辑进行硬编码(hard-code)。虽然使用框架的开发者可以改变组件扩展点的逻辑,但整
个处理流程已经固定了。当业务规则的改变引起组件中的处理流程需要改变的时候,这种被硬
编码的处理流程就暴露出了其缺乏灵活性的缺点;由于处理流程和协调逻辑被固定了,你不得
不更改已存在的组件,或者是写一个新的代替它。白盒框架的另一个缺点是,它通常要求开发
者了解很多框架组件的实现细节。当开发者实现框架组件中的抽象方法时,他经常需要在实现
代码中引用抽象类的方法和变量,这就使了解框架组件的内部细节成了使用框架组件的先决条
件。
黑盒框架通常采用组合法解决白盒框架遇到的难题,但是黑盒框架也有它的缺点。
2.3.4 黑盒框架

黑盒框架由直接可用的具体类组成。使用黑盒框架时,尽管开发者能够继承已存在的框架
组件来达到客户化的目的,但更为通常的方法是,通过组合很多组件来达到预期目的。黑盒框
架可能包含很多通用点,它借助组合法来支持扩展点。图 2-8 说明了一个黑盒框架。

由于黑盒框架使用了组合法,它提供了比白盒框架更大的灵活性,开发者能够任意挑选
不同的组件来满足特定的应用需求。白盒框架通常要求开发者了解框架组件的实现细节;黑盒
框架则不同,它由隐藏了内部实现的组件组成。使用这些组件只需通过定义明确的接口——比
如特定的公共(public)方法和属性(property),开发者要使用框架,只需熟悉这些公共成员
就行了。
和白盒框架相比,黑盒框架较难开发。把业务领域的专门知识封装到组件,满足很多应用
场景的使用,并非易事:封装的东西太多,会导致组件难于在很多场景中使用;封装的东西太
少,应用开发者就要开发大量组件并使协调逻辑更复杂。
黑盒框架优秀的灵活性不是免费的。当使用黑盒框架时,为了让众多组件协同工作,开发
者必须实现他们自己的处理流程和协调逻辑;既然要控制那些组件如何协同工作,
所以在利用黑盒框架优秀灵活性的同时,开发者还承担了额外的工作量:为组件们“牵
线”。相反,白盒框架在组件的模板方法中则是为你自动处理“牵线”问题。
使用黑盒框架的开发者,尽管没有使用白盒框架时要了解抽象类内部实现的问题,但是
他们必须熟悉更多组件以及它们的使用,因为他们现在要处理更多的“活动件( moving

part)”并把它们组装成需要的“机器”。

开发应用框架时,对框架是由抽象类还是具体类组成,并没有要求。实际上,纯粹的白盒
框架和黑盒框架都是不切实际的。混合使用继承法和组合法,使你可以自由选择适合特定组件
设计的最佳方法;这时,你实际上在创建灰盒框架。
2.3.5 灰盒框架

灰盒框架同时采用继承法和组合法,它通常既包含抽象类,又包含具体类。采用何种方法
支持通用点和扩展点,要视具体组件而定。图 2-9 显示了一个灰盒框架。

注释:</SPAN> 因为包含虚方法的具体类,既能够被直接使用,也能够被另一个类继承,
然后覆盖它的虚方法以改变它的行为;所以,只要这些具体类混合运用了继承法和组合法,灰
盒框架只包含具体类是可能的。
实际中的应用框架最有可能是灰盒框架。特定的组件使用继承法还是组合法,要根据如下
两个判断来决定:这个组件要实现什么功能?开发者在其业务应用中有可能怎样使用这个组件?
当我们选择继承法、组合法或是两者组合的时候,我们应当牢记权衡每种方法的性能、维
护成本和易用性。

从性能角度讲,组合法往往比继承法慢,主要是因为要在运行时加载和访问额外的组件 ,
才能达到预期的结果;当你组合使用多个组件的功能时,也增加了很多额外的调用开销。然而,
使用继承法时,派生类本身通常就包含了大部分必须的功能,因而减少了程序需要创建和访问
的对象的数量,并且消除了使用组合法时调用不同组件的额外代码。
维护成本是选择两种方法时要权衡的另一个方面。使用组合法时,开发者使用的是一组耦
合度很低的框架组件,这使他们的应用更能适应变化,因而更加灵活和更易于扩展。然而,在
应用部署(deploy)之后,负责后期支持的工程师必须面对许多额外的“活动件”,这将导致
额外的维护工作量。例如,业务需求的一个改变可能导致框架组件的改变:原因在于你使用的
是组合法,也就是说,会有一系列的框架组件参与到一个特定的业务功能之中,所以在业务需
求改变时,它们也很可能要跟着改变。且这些发生在多个组件中的改变,还增加了测试和部署
的工作量。另一方面,如果使用继承法,比起组合法显得不够灵活,但是它引入的“活动件”
较少。此时,因为业务功能由一个类层次( hierarchy of classes)提供,所以通常能够通过改变

类层次树(hierarchical tree)中很少的类,就可以解决业务需求变化的问题。当然,应用框架的
实际维护成本取决于框架的具体设计,以及业务需求变化的类型。但一般而言,要处理的“活
动件”越少,维护成本就越低。
易用性也是设计应用框架时应考虑的一个方面。框架组件若采用继承法,通常能将复杂的
协调逻辑和处理流程隐藏到父类或抽象类中,于是,开发者通常只需通过覆盖实现少数方法
(method)就能够达到期望的结果。因此,只要不要求开发者去了解那些多得难于招架的父类
细节,继承法的易用性还是很好的。组合法的易用性很大程度上依赖于开发者需要使用多少组
件才能达到特定结果:使用一组松耦合的组件,常要求开发者了解它们,并亲自编写协调逻辑
和处理流程来组织它们之间的协作,才能达到期望的结果。然而,如果你愿意牺牲灵活性,创
建少数粗粒度的组件,以使开发者只需使用为数不多的组件就能达到目的,那么,框架就会变
得更易用,开发也更有效率——因为这样做显著减少了开发者必须了解和编写的协调逻辑。
当你进行很多框架设计决定的时候,你必须牢记:没有银弹。你需要在不同的方法之间权
衡——决定如何平衡它们,并创造出适合你的目的的应用框架,这就是你的工作。
2.3.6 设计模式

当你设计和开发应用框架时,你常常会碰到重复出现的设计难题,例如,如何提高处理
流程改变的应对能力,如何改进特定应用的定制能力。设计模式描述了软件设计的共性问题的
解 决 方 案 , 有 助 于 你 解 决 开 发 应 用 框 架 中 的 一 些 共 性 问 题 。 在 Erich Gamma 、 Richard

Helm 、Ralph Johnson 和 John Vlissides——即“四人组(gang of four,GOF)”——的经典书籍

《设计模式:可复用面向对象软件的基础》中,记录了许多普遍使用的设计模式,下面的列表描
述了其中一部分设计模式及它们能解决的问题:

策略模式(Strategy):处理应用中算法变化的设计。它支持开发者以“即插即用”的方
式,定制特定应用的算法。
桥接模式(Bridge):解耦(decouple)应用中抽象和实现的设计。它支持开发者为应用
部件提供不同的实现,而不影响应用的其他部分。
装饰模式(Decorator):提供处理数据的层次方法的设计。它支持开发者方便地装配处理
数据的多个组件。
观察者模式(Observer):提供订阅-分发通信模型的设计。它支持开发者方便地向多个对
象分发信息。
中介者模式(Mediator):阻止多个对象显式相互调用的设计。它支持开发者在不同对象

之间建立松耦合(loosely coupled)的通信。

模板方法模式(Template method):提供算法骨架的设计。它支持开发者在还没有定义算
法如何实现的情况下,定义处理流程和协调逻辑。
访问者模式(Visitor):定义新操作而不改变旧操作的设计。它支持开发者将一个操作与
经常变化的协调逻辑解耦合。
单件模式(Singleton):确保类只创建一个实例(instance)的设计。它支持开发者更好地
控制对象的创建。
抽象工厂模式(Abstract factory):提供创建对象族的接口而无须指定具体类的设计。它
支持开发者减少应用中对具体类的引用,从而减少具体类发生变化时需要改变的代码量。

本书的其余部分,我们将看到,这些设计模式如何帮助我们开发应用框架,以及如何使
用.NET 平台实现它们。
在本章中,我们了解了应用框架开发的过程和技术。首先,我们讨论了组成应用框架的不
同层,以及每一层和其他层的关系;然后,讨论了框架开发过程,它包括以迭代方式进行的分
析、设计、实现和稳定四个阶段,还讨论了每一阶段的具体任务;接下来,学习了框架开发方法
例如白盒框架、黑盒框架和灰盒框架;我们也讨论了一些关键的框架开发技术,包括通用点、扩
展点和设计模式。 在本书的其余部分,我们将通过例子讨论如何实际设计和实现应用框架:以
一个框架范例为参考,向你展示如何运用.NET 技术和设计模式开发应用框架。

3.1 什么是 SAF

开始,我绞尽脑汁在考虑开发一个什么样的框架案例,并着手从现有的软件产品和我以
前的经历中搜集想法;最终,我决定为 B2B(business-to-business)应用创建一个框架。相对而

言,识别大多数客户化应用( customized application )中的通用服务比较容易,但是识别有些


特定领域的框架组件要困难得多,因为只有十分了解业务领域才能发现它们。所以,我认为必
须使用一个好的案例,它应当提供易于理解的特定领域组件,方可使本书完美。我曾在几个项
目中使用过 Microsoft BizTalk Server(Microsoft 于 2000 年首次发布的服务器产品,允许公司快
速建立一个系统,和它的商业伙伴交换和处理商业文档),并写了一本关于它的书,因此我对
典型的 B2B 应用有很多想法,于是选择了 B2B 领域的框架作为案例。我简要地列出许多对 B2B
应用通用的功能,作为本书所创建框架案例的一部分。
我把这个框架命名为 Simplified Application Framework,简称作 SAF。我的目的是创建一个
易于理解的框架项目,通过刚好足够的代码实现预定目标,并使读者了解我的观点。
SAF 由两组框架组件组成:第一组是通用的跨领域组件,第二组是特定领域组件。第一组

包含了诸如类工厂(class factory)服务、缓存(caching)服务、事件通知(event notification)服


务等通用组件。不管是哪种业务领域的应用,这些服务都是通用的,并且这些服务中的特定领
域知识非常少。相反,第二组包含的特定领域组件只在 B2B 应用中通用。B2B 应用的本质是在

商业伙伴之间交换和处理商业文档。在很多 B2B 应用的开发中,以多个处理层( processing

layer)方式来处理文档对象的组件是很有用的,SAF 有一个组件正是完成该目的。

在第 2 章,我们讨论了应用框架的不同层,SAF 也有类似的分层结构。图 3-1 说明了 SAF


的分层结构以及每一层中的组件。

在该分层结构的底部是 .NET 框架,它包含了提供基本功能的多组类,每个 .NET 应用都

构建于其上。实际上,每个.NET 应用都以.NET 框架作为其分层结构的最底层。在.NET 框架上面

是 SAF 的跨领域组件层,图中该层包含十个组件,这些组件提供了对许多.NET 应用通用的服

务。本章稍后将简要讨论每个组件,而在接下来的 10 章中,分别对 10 个组件进行详细讨论。在

SAF 的跨领域组件层之上是 B2B 领域组件层,它们是些专门针对 B2B 应用的组件。最上层是客

户化应用层。客户化应用的开发,会使用.NET 框架、SAF 跨领域组件和 B2B 组件等多个层。


如图 3-1 所示,SAF 包含 10 个基础组件和两个领域组件。我打算用这 12 个组件作为案例,

通过对它们中的每一个进行深入讨论,来说明如何设计和开发应用框架。在接下来的 12 章中,

我将采用列出每个组件的目的和好处的方式来讨论它们。对开发这些组件时用到的一些.NET 技

术也会进行讨论;例如,讨论类工厂服务时说明 .NET remoting 技术和.NET 反射(reflection)

技 术 , 讨 论 缓 存 服 务 时 说 明 XML 技 术 , 讨 论 事 务 ( transaction ) 服 务 时 说 明 服 务 组 件

(ServicedComponent)技术,等等。框架组件使用的.NET 技术中,有的比较基本,但有的有点
儿难;我对二者都会进行简要介绍,之后才开始深入到框架的实际代码中。设计模式在框架中
很常用,能解决许多设计问题,它们是处理应用不同部分之间复杂关系的优秀技术。很多 SAF
组件都使用了设计模式作为支持具体实现的面向对象技术,以增强灵活性和可定制性。通读了
本书各章后,你将学到这些设计模式,并看到它们在 SAF 组件中的实现,最重要的是,你还将

理解为什么特定的模式对特定的 SAF 组件有用。

本章的其余部分,对每个 SAF 组件做一个快速浏览。

3.2.1 类工厂服务(ClassFactory Service)

当开发者使用业务类开发应用时,我们应该尽可能地减少对他们指定具体业务类的要求。
SAF.ClassFactory 服务允许开发者在开发应用时,不必在代码中指定具体业务类就可获得业务

对象。从而,当具体业务对象改变时,可以减少甚至避免相关代码的改变。因为 ClassFactory 把

业务对象的创建从使用它们的程序中抽取出来,于是只需把特定对象的实例化(instantiation)
逻辑添加到类工厂服务中;另一方面,要改变对象的行为,也无须开发者大量改变代码和投入
额外精力了。
SAF.ClassFactory 中使用的两项主要 .NET 技术是,.NET 反射技术和.NET remoting 技术,

该框架组件的设计还采用了抽象工厂模式和单件模式。我们将在第 4 章讨论该框架组件的设计
和实现。
3.2.2 缓存服务(Caching Service)

SAF.Cache 为框架提供对象缓存能力。应用开发期间,开发者经常希望通过消除过多的对

象创建来提高应用的性能——他们经常是把已存在的业务对象保存起来,过后再去重新获取它
们,以访问其属性和方法。对象缓存服务对所有类型的业务应用都是通用的。创建缓存服务,并
使其作为框架的一部分,不仅减少了开发者的工作量,也标准化了整个应用的对象缓存机制。
SAF.Cache 为框架提供了对象缓存服务,它有两个主要功能(feature):第一,它特别采

用了 XML 技术,使被缓存的对象具有层次化结构,这使得开发者管理缓存对象更加容易。第二,
它内置了钩子方法,这使得开发者能够在不改变框架代码的情况下,改变缓存行为和算法以适
应特定业务需求。
在 SAF.Cache 中使用的主要.NET 技术是 XML 和 XPath,它们由.NET 框架提供。在第 5 章,

我们将讨论如何运用这些技术和策略设计模式来实现 SAF.Cache。

3.2.3 配置服务(Configuration Service)

SAF.Configuration 为不同应用在运行时刻存取配置信息提供了统一服务。一个业务应用经

常由几个单独的应用程序组成,并可能由多个团队来开发。所以,建立一个标准的配置信息存
取机制,有助于减小管理开销,并使代码更加易读,因为应用的不同部分存取配置信息的方式
是相同的。
SAF.Configuration 使用了.NET 配置技术,并为开发者提供了一个易用的手段来定义特定

应 用 配 置 信 息 。 SAF.Configuration 为 开 发 者 统 一 了 配 置 信 息 设 置 机 制 , 并 引 入 了 强 类 型

(strongly typed)配置对象的概念,这简化了读取配置信息的应用代码。

在 SAF.Configuration 中使用的主要.NET 技术,是.NET 的配置文件和配置设计。在第 6 章,


我们将更加深入地讨论该配置组件和强类型配置对象。
3.2.4 事件通知服务(EventNotification Service)

EventNotification 服务为业务应用提供一个事件通知模型。应用某一部分(通常是独立的

进程或远程系统)激发的事件,可能是其他部分行为的基础,这种情况十分普遍。若没有预先
定义的事件通知架构,每个开发团队都要创建自己的事件通知系统,这将导致集成各团队的事
件通知时的种种困难。SAF.EventNotification 通过在框架层提供事件通知组件,来解决上述问题。
这减小了每个开发团队创建自己的事件通知的开销,因为现在他们都可以使用相同的标准来发
送和接收事件,从而免去了额外的集成工作量。
SAF.EventNotification 使 用 了 两 种 .NET 技 术 : 委 托 和 .NET remoting 技 术 。

SAF.EventNotification 有两个主要目的。第一,它为开发者提供一种跨进程发送和接收事件的标

准手段。第二,当事件通知涉及到多个应用时,它省去了集成的工作。SAF.EventNotification 的
架构基于观察者模式和中介者模式,这些我们将在第 7 章深入讨论。

3.2.5 “Windows 服务”服务(WindowService Service)

在 Windows 平台上,启动并持续运行一个应用的最常用手段之一,就是 Windows 服务。

例如,你可以创建一个 Windows 服务来容纳一个监听者对象,不断地监视消息队列或网络端口

等的输入数据。Windows 服务和管理工具中的服务控制台紧密集成,可以将它们配置成随操作

系统一起启动。然而,对每个 Windows 服务,都必须创建和发布一

个服务安装程序。如果你的应用系统要求有多个 Windows 服务,就会导致开发和发布的额

外工作量。SAF.WindowService 通过创建统一的 Windows 服务机制来解决上述问题:开发者只

需 简 单 修 改 配 置 文 件 , 就 可 以 增 加 另 外 的 后 台 进 程 ( background process ) 。 有 了

SAF.WindowService,开发者就不再需要创建和发布多个 Windows 服务项目及其安装程序了;

相反,他们只要简单地创建多个业务类,并修改配置文件中的设置就可以了,这些业务类将在
系统启动之后被加载,并持续在后台运行。
SAF.WindowService 中使用的主要.NET 技术是.NET Windows 服务、反射和线程。模板方法

设计模式用来创建扩展点,开发者可以在扩展点处插入他们自己的关于如何运行服务的业务逻
辑。我们会在第 8 章深入讨论这些。

3.2.6 消息队列服务(MessageQueue Service)

在应用开发中,消息队列是最被低估的技术之一。它对客户和服务器应用的解耦能力,使
你 能 够 开 发 出 具 有 高 度 可 伸 缩 性 ( scalable ) 和 容 错 能 力 ( fault tolerant ) 的 异 步

(asynchronous)应用。消息队列在应用集成的开发中也很常用,以支持异构环境中的应用之间

发送和接收数据。为了利用消息队列,开发者常常要学习不同消息队列技术的 API 集,例如

MSMQ、MQSeries 或者客户自己内部使用的队列技术。SAF.MessageQueue 服务提供一个 API 集,

使得开发者不必关心具体采用了哪种队列实现技术,就可以使用消息队列机制来编程。
SAF.MessageQueue 服务还允许你插入自己的队列实现机制,而无须让开发者学习新的 API 集。

SAF.MessageQueue 使用 System.Messaging 命名空间来访问 MSMQ ,使用 .NET 的 interop


功能来访问 MQSeries 的 COM 动态链接库。它还使用桥接设计模式来解耦用户接口和不同队列

实现技术之间的依赖关系。我们将在第 9 章深入讨论 SAF.MessageQueue 是如何工作的。

3.2.7 授权服务(Authorization Service)

授权是一个过程,根据调用者的标识或所属用户组( group)来判断调用者是否有权访问

置于保护之下的特定资源。 SAF.Authorization 服务为开发者提供一种简单途径,使他们能够为


应用增加授权检查,同时又不会使安全检查代码和应用代码耦合在一起。使用
SAF.Authorization,开发者只需简单地在方法( method)上增加特性( attribute),就能够在方

法上进行安全检查——例如角色级检查或用户级检查等。这种定义组件的安全特性( security

attribute )的方式,和 COM+ 很相似;它提供的是一种声明性安全( declarative security )功能

(feature),即是在高于组件本身的层面、以独立于组件的方式进行的。

.NET 直 接提供的安全特性,要求在应用代码中定义用户或用户组的访问权限 。 而

SAF.Authorization 的设计解除了这种要求——开发者可以通过改变配置文件中的设置来改变业

务对象的安全权限,而无须改变任何应用代码。
实现 SAF.Authorization 服务所使用的技术包括:.NET 声明性安全、.NET 特性以及.NET 反

射。SAF.Authorization 服务还允许开发者通过策略设计模式,来改变安全权限检查方法。在第 10

章,我们将深入讨论 SAF.Authorization 服务。

3.2.8 身份验证服务(Authentication Service)

身份验证是识别调用者身份的过程,是应用中安全检查功能的先决条件。一个应用经常包
含很多子系统,每个子系统都有它自己的识别具体调用者的方法——通常是通过诸如用户名和
密码这些凭证(credential)。当一个调用请求要涉及到多个子系统时,这就产生了问题:该调
用必须提供多组凭证,这就会显著增加开发负担,并使每个子系统和其他子系统的身份验证逻
辑紧紧耦合在一起。SAF.Authentication 服务通过为整个应用提供单点登录( single-sign-on)功

能,解决了上述问题。有了 SAF.Authentication,调用者就不必提供多个凭证了,而是只需提供

一个凭证,就可以获得整个应用系统中所有应用或子系统的访问权。 SAF.Authentication 为调用


者进行凭证映射(credential mapping),免去了开发者在应用中编写此类逻辑之苦。

SAF.Authentication 通过像 .NET 安全 Web 服务( security Web service )这样的技术,来实

现单点登录功能。在第 11 章,我们将深入讨论该服务。

3.2.9 加密服务(Cryptography Service)

SAF.Cryptography 为应用提供数据的加密和解密服务。不管是密码还是需在网络上传输的

数据,应用开发者经常需要将某些敏感数据加密。 SAF.Cryptography 为此提供了简单的手段,


使开发者不必大量研究安全技术,就能够达到该目的。
SAF.Cryptography 使用了.NET 加密、Web 服务增强(Web Service Enhancement,WSE)以

及.NET remoting 等技术,我们将在第 12 章讨论它。

3.2.10 事务服务(Transaction Service)

SAF.Transaction 为开发者提供一种灵活的方法,以使应用支持事务。在 .NET 中,分布事

务(distributed transaction)是通过 COM+组件来支持的。然而,如果不重新组织方法调用流程,

并且创建大量 COM+组件的话,要实现跨多个方法调用的事务是很困难的。 SAF.Transaction 使

支持跨多个方法调用的事务变得简单,并缩减了 COM+控件的数量,而不管应用中要支持多少
事务。
SAF.Transaction 使用.NET 中的 COM+实现。我们将在第 13 章深入讨论 SAF.Transaction 服

务、ServicedComponent 类以及.NET 框架中的其他 COM+功能。

本书的最后两章将介绍最后两个框架组件。B2B 应用的业务需求非常简单。我希望创建一
个应用,它能够接受业务伙伴从因特网传送来的输入文档;在进行内部业务处理之前,还要进
行几项服务性处理,例如数据解密、文档结构转换和文档日志记载等;之后,文档将被传递给
一个工作流模块,进行一系列业务任务的处理;待文档被处理之后, B2B 应用可能产生一个输

出文档或收据,并将其通过不同协议返回至发送方。图 3-2 说明了该 B2B 应用的高层处理流程。


这 个 典 型 的 B2B

流程图由三组处理组成:输入文档处理、工作流处理和输出文档处理。
输入文档处理包括一系列文档处理层(document processing layer),它们对输入文档进行
很多服务性处理。文档处理层的一些例子包括:文档解密、文档结构转换、文档日志记载以及往
文档中写入特定应用数据等。
输出文档处理的概念和输入文档处理十分相似,只是施加在文档上的操作是反方向的。然
而,情况并非总是要如此,施加在文档上的操作可以增加、减少或根据应用的业务需求重新安
排顺序。
诸如更新库存、处理新订单和更新数据库这些真正的“业务”任务,在工作流处理中进行
处理业务文档经常涉及到企业之内的多个应用和系统。工作流处理的主要任务是:识别参与的
应用,把它们连接起来使特定业务文档的处理自动化。
因为 B2B 应用都或多或少地适合上述模型,所以我们可以使用该模型作为高层设计来为

B2B 应用开发特定领域框架组件。有了这些组件,B2B 应用变得更容易开发,也变得更加灵活,

可以更优雅地应付业务规则和需求的变化。
SAF 有两个支持 B2B 应用的特定领域组件:DocumentLayer 服务和 Workflow 服务。

3.3.1 文档层服务(DocumentLayer Service)

SAF.DocumentLayer 服务为开发者提供一种易用的方法,来创建单独的文档处理层。一方

面,各处理层之间相互解耦;另一方面,它们共同协作,来处理输入文档和输出文档。使用
SAF.DocumentLayer 服务,开发者能够很容易地通过配置文件,来定义特定文档应当如何通过

每一处理层被处理。当一个新的处理层要被引入系统时,开发者也只是通过配置文件将新的处
理层插入到现存处理层之间,而不会涉及现有的应用代码。
SAF.DocumentLayer 的核心是装饰设计模式,它使你能够以“逐层”的方式对一个对象进

行服务性处理。在第 14 章,我们将深入地讨论该框架组件。
3.3.2 工作流服务(Workflow Service)

当一个业务文档通过 B2B 应用时,许多业务组件都参与对该文档的处理。你必须管理这些


业务组件的协调问题,以保证文档能够以正确的顺序处理。随着这些业务组件数量的增加,更
多的组件需要相互协作,所以协调逻辑会变得越来越复杂。当处理文档背后的业务规则有所改
变时,无论是组件还是组件处理业务文档的顺序,都可能严重改变。
SAF.Workflow 服务通过将处理文档的业务逻辑和控制处理流程的协调逻辑解耦,减小了

这些处理的复杂性。一方面,如果多个组件相互协作的规则有所变化,或者增加了新的规则,
我们只需改变工作流的协调逻辑;另一方面,如果一个具体组件处理文档的规则有所改变,我
们只需改变那个组件。
SAF.Workflow 借助访问者设计模式,来隔离工作流的业务逻辑和协调逻辑,并使用户易

于向工作流中引入新的协调逻辑和业务组件。在第 15 章,我们将深入地讨论 SAF.Workflow 及


其实现。
SAF 的 12 个框架组件,每个都带有一个可执行的 C#项目,它们演示了这些框架组件的

目的,以及在应用中如何使用这些组件。读过每章之后,我建议你把测试项目加载到 VS.NET
中测试一下。你也会发现,当我讨论每一个框架组件时,都有针对重点代码段的“代码分析”
这一节。但是,没有比通过 debug 方式运行程序并单步跟踪到每一行代码中去看看背后到底发
生了什么更能说明代码含义的了。每个框架组件的源代码放在一个被适当命名的文件夹中,例
如,SAF.ClassFactory 的源代码被放在“SAF.ClassFactory”文件夹中。SAF.ClassFactory 的测试项

目放在“Test.SAF.ClassFactory”文件夹中。

如果你想同时看所有 SAF 组件的源代码,你可以在 VS.NET 中加载名为“SAF.sln”的解决

方案文件,这样所有 SAF.*项目就加载到 VS.NET 集成开发环境中了。

SAF 伴随本书而创建,其目的有二:一是为了向读者展示,可以在应用框架中开发不同

服务;二是用它作为一个框架实现的案例,来说明开发框架时可以使用的各种 .NET 技术和设


计模式。我尽量保持代码的简洁,使它易读和易理解。我省略了很多通常对于实际生产环境所必
需的、有关异常处理(exception handling)、线程安全和各种验证的代码。SAF 用于教育目的,而

非实际生产用途。然而,欢迎你用 SAF 作为起点,修改它以满足你特定场合的需要。对随书提供

的 SAF 源代码,没有使用和修改的限制。
在本章,我们对 SAF 做了一个概览,描述了它的目的和架构,以及如何将它用于构建客

户化应用。我们也讨论了 SAF 中的每一个组件。通过对 10 个跨领域框架组件和 2 个特定领域组

件的简单介绍,你已经了解了每个组件背后的思想、附加价值、.NET 技术和设计模式。在下一章,

我们将讨论 SAF 的第一个组件:类工厂服务。

You might also like