纵横组件真经之华山论剑 -- 软件行者 [an error occurred while processing this directive]
SoftwarePractitioner.org


首页

作文

翻译

随笔

本站

English
  


纵横组件真经之华山论剑

Software Development Magazine的组件专栏回顾

胡健
2006年12月

“Software Development Magazine”( http://www.sdmagazine.com)从1999年10月期开辟了一个栏目 “Beyond Objects”,主要是讨论基于组件的软件开发(component-based development)。 该栏目编辑开篇道:此栏目旨在提供严谨、深入和及时的思想(careful, thoughtful and timely insights),以帮助软件开发者了解组件技术。该栏目持续了3年,至2002年 9月落幕。该栏目吸引了好几位顶级专家的参与,众位专家从不同的角度对这个问题发表了 自己的观点,对组件的方方面面进行了深入的探讨,其中尤以Clemens Szyperski与Bertrand Meyer之间的诘问辨析最为精彩。本文依专栏文章的时间为序,对Szyperski与Meyer之间 的主要讨论/辩论以及他们对组件开发的观点作一个回顾。

组件的定义

Oct 1999 Clemens Szyperski: Greetings from DLL Hell

Clemens Szyperski首先登场。此君以“Component Software - Beyond Object-Oriented Programming”一书奠定了他在组件技术上的领先地位。他首先指出组件技术,即通过装配 组件来构造软件,为下一代软件工程带来了很大的希望,他甚至更一步将目前的开发技术之 于组件技术比喻为精巧的手工制造之于工业革命。

接下来他在这篇题为"Greetings from DLL Hell"的文章里,提出了软件的“相对退化” (relative decay)问题,这主要是由于业务需求变化而使系统功能相对退化。因此,我们需要 对系统进行更新。把整个系统推倒重来,这是不太现实的。一种做法是修改部分源码,重建一个 更新版的系统。还有一种方法是更新相关的“软件单元”(software units)。这些单元在UNIX 系统中是以shared object(.so)文件形式存在,而在Windows系统中是dynamic linked library (.dll)。

阻碍一个"单元"成为一个能被自由更换的组件的主要因素,是单元之间的依赖关系,这是组件技术 要解决的一个基本问题(其实也是软件设计开发中的一个基本问题)。 当新单元的行为由于种种原因与原来的单元不同时,就有可能使系统出错或瘫痪。具体地说,

  • 单于A依赖于B的实现细节,而单元B的开发者却不知晓。
  • 接口规范说明不是足够精确、清晰。
  • 单元A和B皆耦合于第三者,如全局变量。

作者然后给出了组件的简略定义:如果被共用的软件单元准确地说明了所依赖的单元并严格避免未 在文档中记录的耦合,那么这样的单元可以被称为组件。(If your shared units declared which other units they depended on and strictly avoided undocumented coupling, then you could call them components.)作者紧接着指出,实际上还有其他的一些标准,包括需要的单元 的接口和可部署性(deployability)。

Nov 1999 Bertrand Meyer: The Significance of Components

紧跟着下一期是Bertrand Meyer上场。Meyer是一位Object-Oriented技术的大师,其代表作 “Object-Oriented Software Construction”是OO技术的经典之一。他在这篇题为 “The Significance of Components”的文章中第一句就开门见山地问道: “What is a component?”,并提出有狭义和广义两种答案。狭义答案是Szyperski书中提出的 “二进制”(binary)形式的软件单元,如COM,EJB和CORBA。广义的定义则是Meyer提出的 组件是“Client-oriented”的软件部件,它具备两个特征:一是为其他程序单元(即它的 client)所用,二是组件的编写者不需要知道使用者(即client)。

Meyer进一步说,这两个定义的不同,导致了对组件与对象间关系的不同看法。Szyperski 著作的副标题是“Beyond Object-Oriented Programming”,而Meyer坚决认为组件技术 是OO技术的自然演进。他以狭义定义中的组件的binary特征为靶子,问道,组件的 binary形式有什么特别之处能引起众多的重视,答曰:binary组件最终使得信息隐藏 不可避免(binary components finally make information hiding inevitable)。 接下来,Meyer从David Parnas 1972年发表的两篇关于信息隐藏的划时代的论文谈起,说明了 信息隐藏在构造大型软件中的重要性,同时指出现在流行的程序语言都没有完全彻底地“强制性” 实现信息隐藏,如允许a.x=b这样严重违反信息隐藏原则的语句。他认为,只有当语言和环境能 够保证信息隐藏的实现的时候,信息隐藏才有意义。如果语言不能保证,那么“二进制”形式 将扮演起这个保证的角色。

Feb 2000 Clemens Szyperski: Point, Counterpoint

Meyer对binary形式的解读引起了Szyperski的强烈反应。在这期题为“Point, Counterpoint”的 文章中,Szyperski就组件的binary形式以及组件和OO的关系展开了辩驳。

首先,他完全不同意Meyer关于binary形式的根本目的是保证信息隐藏的观点。他认为所谓 binary是指“机器可执行”(machine executable),它并不是指某个处理器的机器代码,也不是 指人不可读的(not readable by humans)。组件采用binary形式的首要目的不是为了信息隐藏, 不是为了保护知识产权,而是因为我们需要组件是“部署单元”(unit of deployment), 即这个单元扔到系统里就能被执行。

接着,Szyperski指出,更广义来讲,组件可以是能被组合的一种东西(anything that can be composed)。人们有时也提到设计组件(design components),架构组件(architectural components)等,但它们是在工程师们之间交流的智能工件(intellectual artifacts), 而不是可执行的部件。Szyperski进一步分析,源码为什么不是组件。因为源码如果要能执行 的话,还需要很多其他的信息,例如应使用的编译器和运行程序库(runtime libraries)。 在脚本语言中(scripting languages),源码和二进制码是一回事。一个“脚本单元”投入 目标系统时,在相应的运行系统中得以解析。Szyperski认为,理论上讲,我们可以 接受这种“二进制”形式的脚本代码,而实际上是否真的可行又要另当别论了。

关于组件与对象,Szyperski首先声明他所说的“超越对象”(Beyond Object-Oriented Programming)并不是不要对象,而是说组件具备一些对象所没有的性质。然后他进一步来辨析了 组件与OO中的类的关系来说明组件不是对象。

  • 第一,他认为OO中的类并没有明确表明它所提供的和它所需要的。当然,通过接口可以说明它 所提供的服务,而需求却一点都没明确说明。
  • 第二,一个类的构造一般需要使用继承,实践中几乎不大可能把基类(base class)和子类 (subclass)分开到不同的部署单元。
  • 第三,一个组件一般都包括多个类,而这个组件的功能的表达是通过一些“边界处的对象” (objects at the boundary)。系统中的其他部分就是通过这些对象来和这个组件交互作用的。

另外,他还指出,组件并非一定要用类来建立。用过程(procedure)来实现的组件仍然是组件, 它不是在运行时产生类的个体(instance),而是生成功能/函数闭包(function closures)。

针对Meyer关于组件的目的是复用(reuse)的观点,Szyperski也进行了反驳。他认为复用 固然是经济上和技术上的一个重要因素,但不是主要驱动因素。两个关键的驱动因素是可扩展性 和可演进性(extensibility and evolvability)。可扩展性是指新的部件能被加入,可演进性 是指旧的部件能被新的替换掉。

Mar 2000 Bertrand Meyer: What to compose

针对Szyperski的辩驳,Meyer在题为"What to compose"的文章中首先不无幽默地说到,希望读者 不要认为这是他们两人的一场叫喊比赛。这个专栏的目的是让作者们能充分发表自己的观点,让读者 更好地理解这个问题。

Meyer然后更正道,他不是说“二进制”形式的全部目的是为了信息隐藏,而是说信息隐藏是 “二进制”之所以吸引人的关键之处。但Meyer仍然质疑Szyperski的组件只能是二进制形式的观点, 并进一步说明他所理解的源码和二进制形式。他说,老年间(大约1992年),源码是指象C和Pascal 这样的东西,而二进制是指处理器上可运行的代码。但是现在的bytecode和脚本代码(scripting code) 也被看成二进制码,那么象Eiffel这样的高级语言,可以用just-in-time编译器来处理并生成bytecode 或C或两者的混合,那么用这种高级语言写的组件又该算作哪一类呢?

Meyer还质疑“组件是部署单元”(unit of deployment)的定义。他认为,从传统意义上说,任何 程序都是一个部署单元。如果我们要把一个程序组件化(componentize),我们需要用一个外壳把 它包起来,这个外壳非常象一个类。实际上,顺着这个思路我们正重新发明COM和CORBA。以此出发, Meyer进一步论述了组件与OO类的关系。他认为,类是组件的正确形式。但这并不是说组件和类是 相同的。在一个OO系统中随手抓一个类,一般不太可能就生成一个合适的组件(正如Szyperski 指出的那样)。但如果有一个或几个类提供了进入系统的接口,那么这些类就给这个系统的组件化 提供了一个良好的基础。如果没有现成的“接口”类可用,那么也可以编写这样的接口类,这对一个 系统设计者来说不是一件复杂的事情。

Meyer然后扩展了他在“The Significance of Components (Nov 1999)”一文中的关于组件定义, 即组件是一种“面向客户”(client-oriented)的软件且具备两个基本特征,进而推出另外五项 (共七项)特征:

  1. 可以被其他软件单元(称之为客户单元)所用。这排除了传统意义上的为人所用软件。
  2. 组件作者不为客户单元做者所知。这排除了被同一个系统中其他部分所用的例程和类等部件。
  3. 包括对所有硬件、软件和其他组件的依赖关系的说明。
  4. 提供精确的功能规范说明。
  5. 对其使用只能基于所提供的功能说明。
  6. 可以和其他组件相互组合。
  7. 可以迅速而顺利地集成到系统中。

组件的定义暂且放下,Meyer提出了他认为更为迫切的问题:我们要组合什么样的组件?他举了 一个例子加以说明。他有两个除了颜色一模一样的手机,蓝色的在美国和加拿大用,红色的在 其他地方用。有一次在澳大利亚,他在充电时把用于蓝色手机的充电器插到了电源插座上 (充电器倒是没有颜色区分)。由于美国加拿大的电压是用110伏,澳大利亚是220伏,所以结果 大家都能想到,他需要再买一个充电器。一般来说,电子组件之所以是组件,是因为它们需要满足 一定的规范说明。软件也不例外,我们需要对软件组件进行规范说明。在一个组件框架中,我们 一般有一个象CORBA和COM中的接口定义语言(Interface Definition Language -- IDL)。我们 可以依赖参数的类型说明。但这还不够,我们还需要语义的规范说明。类型规范说明就象是插头 的直径的规范说明,只要尺寸对了它也能插到一个电压不对的插座里面。Meyer接着用了"契约" (contract)这个词来指对组件的一个较全面的规范说明,并回答他提出的“要组合什么样的 组件的问题”:最最基本地,要组合装备有契约的组件(“At the very least, contract-equipped components”)。

那么,顺理成章,作为"契约式设计"(Design by Contract)的作者,Meyer认为契约式设计 的机制(已在Eiffel语言中实现)完全可以应用在组件中,并提出了“Contract Definition Language”(契约定义语言)的设想,他坚信这是基于组件的软件开发中的一个中心问题。

组件与契约

May 2000 Clemens Szyperski: Components and Contracts

针对Meyer的二进制的论述,Szyperski在“Components and Contracts”作了更明确的,也是最后 的陈述(因为他认为应该继续探讨组件的其他性质,尽管辩论更有乐趣一些)。他首先引述了 Meyer所说的“老年间(大约1992年),源码是指象C和Pascal这样的东西,而二进制是指 处理器上可运行的代码”,然后道,在更老一些的老年间,一个Fortran程序,其源码是录 在一迭穿孔卡片上,一般在前面还有一些JCL(Job Control Language,即作业控制语言) 语句的卡片, 在机器读入卡片时,前面这些JCL语句用来告诉机器应首先对后面的源码卡片 进行编译。JCL语句卡片加上Fortran 源码卡片就组成了一个二进制形式的软件组件,因为它 可以立即被用于一个自动的执行环境中。Szyperski然后给出了他对“二进制”单元的定义: 如果一个软件单元的目标是一个执行环境(execution environment)它就是一个二进制单元, 而与它是否可读、是文本还是机器码无关。如果一个单元的目标是人(程序员)和开发工具, 那么这个单元是源码单元。

针对Meyer对 "组件是部署单元"(unit of deployment)的辩驳,Szyperski声明说,他从未声称 组件就是部署单元,或所有的部署单元都是组件。他是认为组件是需满足一些准则的部署单元。 结合他的二进制单元的定义,他给出了组件的定义:一个软件组件是一个部署单元,它以二进制 形式交付并需满足一系列的准则。他完全同意Meyer提出的七项准则,特别是第二和第五项, 并认为这两项也说明了为什么要着重二进制和部署单元的原因。

对于组件和类的关系,Szyperski首先提到Meyer也认可不是每个类都能是组件的。但是,对于 Meyer的从OO系统能使组件化容易一些的观点表示异议。从他个人的经验中,他的确看到一些 OO设计很仔细地勾画出了单元之间的边界。但大多数情形并非如此。最常见的问题是类程序库 实现上对继承的强烈依赖以及在整个系统中应该用接口或抽象类的地方用了具体的类。 在非OO系统中也有类似的问题,即各部分牵连太多,很难为组件划定一个明确的界限。但是在 传统上严格的层次系统却使组件化容易一些,因为一个层只能调用下面的层,下面的层不能调用 上面的层。对这种层次系统中的过程就很容易实现组件化。

组件定义争论告一段落。Szyperski对Meyer上期中提出的组件“契约”问题跟进讨论。他也认为 质量是组件的一个关键方面。质量,表现在让组件满足精确的规范说明,这是一个需要努力 去达到的理想。在组件世界,规范应该是存在“组件之间”,是不属于任何组件的一个标准。 IDL有些接近这个定义,但它只能用于定义组件能提供什么。这远远不够,因为“契约”应该 包含提供什么,需要什么,同时还应包括一些质量属性,诸如时间和空间限制、安全模型以及 可测试标准等。另外,Szyperski也对"Contract=Interface+Pre+Post"(契约=接口 + 前提条件 + 后验条件)提出质疑。他认为在OO中,由于“外部可观察到的调用” (externally observable invocation),用前提后验的模型不能完全定义出一个操作或 功能函数的语义(在他的“Component Software”专著中有更详细的说明)。尽管如此, 他还是强调说,前提后验条件仍然是描述一个操作的语义的很好的近似方法,比非正式的 注解说明要好得多。

总之,Szyperski认为,Meyer提议的“Contract Definition Language”(契约定义语言) 的确是一个美好的理想,现在也有一些这方面的研究,但要达到这个目标却并非易事。

July 2000 Bertrand Meyer: Contracts for Components

在这期的专栏中,Meyer对契约进行了较全面的分析。他首先表示赞同Szyperski在专著中 提出的软件组件在直感上不同于其他工程领域的组件的看法,并进一步从规范说明这个 方面加以分析。其他工程领域里,规范和产品是分开的,这似乎没什么好强调的。因为 没有谁能分不清一个电路和电路图,一座桥梁和桥梁的图纸。你不会触摸电路图而遭 电击,也不会从桥梁图纸上摔下去。但是在软件领域,规范和产品(实现)之间的区分 却没有这么直接了当。

对于IDL,Meyer认为其前景不妙。并不是说接口这个概念前景不妙,而是说要单独 用IDL再来写一个接口这种想法的前景不妙。它可能对一些很稳定的组件管用,但长远来 说,这种方法有些缺陷。因为软件是在不断变化的,这种方法要求让软件和接口能同步 变化。实际上,Meyer认为一个组件的规范说明应该包含在它的程序文本中(这也是他 提出的“Self-Documentation Principle”,在Eiffel中,一个类的文档能完全用软件 工具从这个类的程序文本中提取出来)。Meyer更进一步用Eiffel和COM 来举例说, 可以用工具把一个Eiffel写的应用包装成一个COM组件,或把一个其他语言写的COM 组件包装成Eiffel的门面(facade)。

Meyer还认为IDL的另一个缺陷则是对一个操作它只给出了名称以及参数类型说明,却不能 作出语义说明。当然,列出一串可能要甩出来的异常情况(exception)在某种意义上也 是一种语义说明,但这会强迫客户程序大量使用异常处理(这不是一种良好的编程风格)。 另外,只告诉客户有可能会出现什么异常情况,却不告诉他们正常情况是什么,这也 是不能令人接受的。

Meyer还提到了他的两位同事关于契约的观点。他们认为有四个层次的契约:一是类型 契约,如现在的强类型程序语言以及IDL;二是语义契约,如类不变量 + 前提条件 + 后验条件;三是性能(响应时间)契约,这对实时系统有重要意义;四是服务质量契约, 这不太好量化,却是客户能在使用中能明显地感觉到的。要提出一个完整的适合组件的 契约定义语言(Contract Definition Language),Meyer认为条件还不成熟。

Meyer认为现实中可用的规范还是:类不变量 + 前提条件 + 后验条件(这便是Meyer 提出的Design by Contract并在Eiffel中实现)。即使你的语言和环境中没有象DBC这样 的支持,你也应该念念不忘这个原则,应该随时问自己三个问题:保持什么不变? 需要什么?提供什么?

Oct 2000 Clemens Szyperski: Components and Architecture

在这期专栏中,Szyperski先对接口、接口定义语言(IDL)、接口与契约进行了一些 论述,然后提出了组件系统的架构的一个特征(即容器-container/context)。

组件的一个设计目标是独立性,因为一个组件对于环境的依赖越小,它就能越广泛地使用。 使用接口来去耦合以减小依赖性是个很成熟的技术。例如一个组件CustomerManager,它要 应付的不应该是某个Customer的实现,而应该是Customer的规范说明,即Customer的接口。 (例如在Java里有,interface CustomerRole {…}, class Customer implements CustomerRole,那么CustomerManager应该使用CustomerRole)。 而具体对付哪个Customer则应留待部署时再定。

关于IDL,Szyperski不同意Meyer所称的IDL前景不妙的观点。他认为提出IDL的目的其实很 简单,只有一个,那就是能够在组件使用者和实现者之间支持接口的定义。IDL最初 是为了在不同系统和不同实现语言中提供一个共同的基准。它的好处是有了一个公认的标准, 有利于接口的发布和文档描述。缺点是没人用IDL来编程,因此用IDL来描述的接口定义 和有关文档说明对所有的程序员都是陌生的。解决此问题的一个方法是让IDL和实现语言 之间的双向映射标准化,如Java-to-CORBA IDL的双向映射。应该指出的是,虽然双向映射 能够让IDL隐藏起来,但这绝对不是要消除对这个重要的共同基准的标准化要求。还有一点 需要注意,比如用Eiffel定义的一个接口,经过两次映射到Java,对Java方来说可能会觉得 不太自然。还有,用来说明如何使用接口的文档也必须要加以转化。这样的全面的双向 映射(包括接口和文档)也是一个待解决的课题。总之,Szyperski认为IDL是我们要 支持多种语言必须付出的代价。除非我们只有一种实现语言,而在现实中,这几乎是 不可能的。

接下来,Szyperski提到了契约、接口和实现之间的关系。他认为,契约最初的解释是精确 定义了的接口。随着建模中的角色愈来愈多地被采用,对契约的解释也在改变。一些角色 可以用单个接口来表示,但很多角色都需要多个接口来表示。把不同的接口真正地分开, 而不是把它们凑在一起在设计中是很重要的,但这点常被忽略。在实现上,当然可以用 一个对象来实现多个接口(如Java中的对多个接口的继承,C++和Eiffel中对多个抽象类 的继承)。但在组件世界里,契约常常会超越由一个对象来实现的多个接口,即一个契约 会关联着由多个对象来实现的多个接口。这种关联如何表达出来,这是一个有待解决的课题。

最后,Szyperski指出,到目前讨论的契约基本上都是由局域条件构成的相对简单的契约。 对组件而言,契约还会延伸到应用域和系统的层面上。例如,如果我们需要在契约里说明 transaction行为,我们一般就要指定一个transaction模型。Szyperski也认为在应用域 和系统层面上的好的标准可以大大地帮助编写组件的契约和实现。接着,Szyperski以两个 支持transaction的组件如何组合为例,来说明组件架构的一个方面(或特征)。假设两个 组件都有标准的接口来与transaction协调者(coordinator)交互。两个组件都可以发出 BeginTransaction and EndTransaction指令。但如果这两个组件所作的工作应该在 同一个 transaction内完成的时候,这就会出现问题了。

推而广之,组件的组合会与它们共有的系统属性起冲突,如此例中两个组件都直接控制 transaction这个共有"方面"(aspect),那么要达到一个所需的一个总体的“方面” 质量(overall aspect quality)就有困难了(Szyperski特别注解,这里的“方面” -- aspect与新兴的aspect-oriented programming中的aspect是同一个概念)。解决这个 问题的一个方法便是将组件中某个“方面”的控制代码去掉。对这些共有方面由一个 “容器”(Container or Context)来系统地加以控制。该技术在Microsfot Transaction Server (MTS)中实现了对组件的transaction方面进行了系统地管理。其后EJB和CORBA 都是同样的思路。对于这种容器的设计是需要精心考虑的。针对这个架构来设计的 契约会易于编写、更为完全、提升可组合性。对组件而言,在这种契约和框架下也 易于实现、测试和组合。由于基于容器的组合在实践中的广泛应用(如EJB和COM+), 那么组件契约规范也应对这种模式提供支持,这样一来我们又有一个新的挑战了。

组件与服务

Aug 2001 Clemens Szyperski: Components and Web Services

在这期专栏中,Szyperski把目光转向了“Software as a Service”,并阐述了组件与 Web Services的关系。首先他对Web Services和相关的一些标准作了一个类比:

“XML Schema (XSD) instead of CORBA or COM-type systems? SOAP(Simple Object Access Protocol )instead of DCOM, IIOP instead of remote method invocation? UDDI (Universal Description, Discovery and Integration) instead of object traders? WSDL(Web Services Definition Language) instead of interface specifications? WSFL (Web Services Flow Language) instead of workflow definition languages?”

Szyperski认为,服务不是软件(The service is not the software)。我们需要把它们 分开来看,也许将来某时有某人来把它们统一起来。简单地看,一项软件服务是软件 (其服务功能由它的接口来定义)在某个(抽象)机器(即所谓平台、操作系统、虚拟机 以及最下层的物理实在的计算机)上运行来提供的。但是,更进一步看,一项服务 要得以实现还需要有操作者(operating agent)来把机器放置在房子里并进行 日常的操作维护。另外,还有些与软件的服务功能有关的标准,如服务中断的时间 限制、系统安全保证等。这些当然不能靠软件本身能实现。软件可能有这样一些机制, 但这些机制的实施取决于若干操作条件,如电源保证系统以及工作地点的安全保卫措施等。 所以服务实际是操作者、运行机器与软件三者的结合。

再看Web Services,Szyperski认为隐藏其后的一个关键理念是在标准Web平台上通过良好定义 的接口为人或其他Web services提供服务。Szyperski特别指出,正是后者(为其他Web services提供服务)使我们有理由认为Web services是操作者(以及操作者的基础设施) 与软件组件而不是其他任意软件的结合。如果我们这样来看待Web services的话,我们 就不会为那些刚提出的标准与以前的一些标准那么类似而吃惊了。

接着,Szyperski分析道,Web services的出现也是有着合理的原因的。传统的分布式系统 结构和OO技术最中心的同步调用(synchronous invocation)难于对付目前Web时代的异步 性、低响应性、高分散性这种现实。而对付这种极度的分布性的还是一些上一代的过程 编程技术。如异步消息技术(asynchronous messaging);用结构化的文档来表达需传递的 消息,如SGML和EDIFACT;还有就是HTTP等Web协议和标准。

最后,Szyperski认为,把对象的理念扩展成能在Web上的服务是个很严重的挑战, 而象Web services这些标准的提出只不过是个开端。

Oct 2001 Bertrand Meyer: Product or Service

Bertrand Meyer这期的专栏非常特别,他用了一个演讲稿(体裁)来谈论软件是产品 还是服务的问题。这篇稿子看上去是他在苏黎世的瑞士联邦工学院任软件工程教授的 就职演说。文章风格独特,严谨论述之中又穿插着风趣幽默。以下摘要介绍他对这个 问题的思考。

“法律上讲,软件应该是产品,不是服务”(“Legally, it's evident that software is a product, not a service”)。软件可以拥有版权,甚至专利。当你把软件 卖给用户之后,用户可以一次又一次地自己运行这个软件,而可以不用你的参与。 不过,看看下面这个例子。Amazon把他们所创的"一击而购"(one-click purchasing) 技术申请了专利,并成功地阻止了Barnes and Noble使用类似的技术。当然,Amazon 不是指控Barnes and Noble抄袭了他们的产品。实际上,Barnes and Noble极有可能 是用了不同的程序语言,甚至不同的内部算法。但是,Amazon有充分的理由来让法庭 相信用户在Barnes and Noble经历的服务过程与在Amazon所经历的是非常类似的。 从这点来看,软件在法律上讲,应该是服务。因为法庭认为Amazon的专利是保护 它的一个服务过程。“所以,从法律上讲,软件的确是产品,哦不,我是说服务” (“So from the legal perspective, software is really a product. I mean, a service”)。

“从商业上看,软件也是产品”(“from the commercial perspective, software is a product, too.”)。微软为什么如此巨大?那是因为他们销售他们的产品: Windows, Office, Project, Visio等等。作为一家巨型产品公司的Oracle也是这样, 其他的如最初出产Turbo Pascal的Borland,出产1-2-3的Lotus。这些公司是软件工业 的先驱,它们是从1970年代IBM的服务模式中分化出来的,当时IBM的主要业务是卖硬件 产品,而软件是随硬件而走的免费服务。当然,我们也需要看看另一类成功的软件公司, 如EDS, Cap Gemini, Accenture,它们足以让人们认为,“从商业上说,软件的确是 服务”(“software is, commercially, really a service”)。这类公司 真正要做的是“应用服务供应商”(application service provider)。而software house 在法语中叫Société de Services。它们是软件公司,但是如果你问他们每天真的在 干什么,他们会说编程的工作是越来越小,服务已是公司的核心业务。Oracle,作为 一家巨型的服务公司,深知这点,他们会告诉你,在将来Oracle会提供完全集成的解决 方案,用户完全不用去管产品的版本、配置、平台、排错和升级。

一个既是产品又是服务的例子是Adobe提供的把Postscript转换成PDF,Adobe称之为 蒸溜(distilling)。你可以买Adobe的Distiller产品,也可以注册Adobe 提供的服务,通过Web你可以"蒸溜"你的文档。目前有许多产品制造商也象Adobe一样, 用他们的产品提供服务。这似乎已成了一个趋势。如有评论认为微软的 .NET的全部意义就是转到软件的“服务模式”(service model)。

下面这段是Meyer针对Szyperski关于组件与服务观点作的答复,甚为有趣,全段译出:

“如果微软真的这样想的话,那他们可能真的是错了,因为有这么一位来自澳大利亚 的仁兄Szyperski(他的朋友称他Clemens),一直在大力推动他称之为“组件开发” 的主张。那位女士?我现在还不接受提问。在我们那个年代,没人会打断就职演讲的。 我听不见。你说什么?Szyperski不是澳大利亚人。那太糟糕了,我正要说他们那里的 Shiraz酒真正是好。啊,当然了,不是每一样好东西都是澳大利亚的嘛。Szyperski 认为我们的软件应该建立在独存/自我包含(self-contained)的、可部署的组件上。 这似乎不合目前把软件视为服务的时尚。他肯定是知道一些微软不知道的东西。 我希望微软能认真听取他的意见。这位先生?请你等我讲完在提问好吗?什么?你说 Clemens就是在微软工作的?这个,啊,我还能说什么呢?”

“从技术上看,软件也是服务,哦不,我是说产品”。Meyer认为,我们的毕生精力 都是在为软件工程建立一个坚实的技术基础,使之成为一个真正的工程学科。同时, 象其他工程领域一样,软件工程也必须建立在众多坚实的产品上。所有的工程服务也 总是建立在稳固的技术基础和产品上。可以确定的说,我们几十年来的努力已开始见成效, 如果说今天的软件开发比30、20、10年前要好的话,至少部分要归功于我们所用的 工具质量更高。

最后,Meyer也指出,软件中的服务方面总会是占主导地位。在软件历史上,一直有人 想把软件构造看成是钉子加木板,而否认其中的创造性甚至艺术性的成分。“自动编程” 的理想也在一定程度上推动了技术的发展,从汇编、COBOL、第四代语言到目前的OO技术 和组件技术。但贯穿于技术发展,正是程序员和他们的服务使得软件生存下来,不管 管理人员是否把软件开发看成是流水线生产。

Jan 2002 Clemens Szyperski: Services Rendered

这期专栏Szyperski开篇就说到Meyer的“Product or Service”一文对他的关于服务、 组件的看法以及行文风格都有“实在”(sobering)的影响。对于就职演讲被打断深表遗憾, 他相信Meyer一定会培养塑造出一批又一批的学生出来。接下来,Szyperski在整篇文章中发展 了他在他的前一篇专栏(Components and Web Service)中提出的“服务是操作者与软件 的结合”的观点,进一步阐述服务与组件或更一般的软件的区别。

首先,操作者是一个实体,是特定的组织或个人,商务世界中的如合同、需求、解决方案、 罚款、付款都是发生在这样一些实体上。

接着,Szyperski着重辨析了组件的契约式规范和法律上的契约/合同的区别。一个组件上的 契约规范说明了这个组件要正常工作所需要的边界条件。还有一些间接的条件如对基础结构 的要求。同时,这个规范还是组件生产者和消费者之间的一个契约。消费者与生产者之间法律 上的合同也许会建立在这个规范之上,但并不是一定会这样,实际上,合同一般会省略技术 细节。值得注意的是,契约规范并不是很容易地能转译到合同上去。一个组件能否正常工作 取决于很多因素。如基层结构和其他相关组件的供应者,以及决定如何对相关组件进行组合的 人员等等。如果出了一个问题,很难说清楚会是哪一方的责任。

而在服务层面上(service level),所有的平台或组件等这些技术方面都变得很抽象 了,而正是服务供应者的责任来提供合同所规定的服务,而同时承担商业风险。另外,象 不间断运行时间、交易处理速度、系统冗余程度、以及(网络)响应时间等都不是软件本身 能完全控制的。

除了从软件契约与服务合同的的不同来阐述软件与服务的区别之外,Szyperski还详细考察了 Object reference/identity在OO软件上和在服务上的不同。在OO系统中,把object reference 传来送去是OO里的一个基本(或根本)方法。但在服务的情形下,一个“对象”的标识指称 (identity reference)可能并不存在,如在SOAP中,一个请求只能给出URL(作为定位)和 其他逻辑名称,而并不是Object reference。而对Web services来说,每次都需要对URL和逻辑 名从头解析,而同样的两次请求是否能解析到同一个对象则不能保证。

如果Web services要传递“对象”的话,那么必须要使用特定的应用域的标识,如帐号、执照 号码等。因此,Web services可以随时改变下面的OO层面上的系统,如部件组成以及基础 结构,在极端的情形下,甚至不用停止Web services的服务。

所以,Web services的合同和对“对象”的标识都是在服务的层面上,针对的是由WSDL定义的 Web services接口。Szyperski认为,Web services不是object或其他软件抽象,而是超越了 Object的更高一个层次的抽象。

组件的系统架构

Aug 2002 Clemens Szyperski: Universe of Composition

在这期和下期的专栏里,Szyperski考察了组件之间的组合方式,并认为要全面了解组件就必须要 彻底弄清组件的组合方式。

Szyperski列举了(当时)三种主要的组合方式:Object-oriented, connection-oriented 和container-based。

Szyperski认为由于object的组合是不太可能的。一个object一般需要调用其他objects来完成 一个功能,如object1的一个method需要调用object11和object12,而object11和object12又需要 调用其他objects。假设另外一个object2也需要调用其他一些objects以完成它的功能,那么很有 可能在某个地方产生"偶合/依赖",如都对某个"全局变量" 进行存取。如果另一个object3的功能 规范和object2一样,但在实现上有可能和object1没有偶合或有不同的偶合,那么object1和 object2的组合与object1和object3的组合就会产生不同的结果。因此,由于传递依赖 (transitive dependencies)的原因,objects一般是不可组合的。

切断对其它类的依赖的一个方法是把这种显性依赖变成参数依赖(parametric dependency)。 最常见是用接口来代替要使用的类,并把这个接口作为一个功能调用的参数。一些语言库提供 的对数据流进行数据处理的类就是用这种方法来与实际实现数据流的类相连的。如Java中 DataOutputStream的构造子便是DataOutputStream(OutputStream out),其中OutputStream 是个接口。任何实现这个接口的类都可以和DataOutputStream相连。另外一个更广泛使用的技术 是event/listener模式,处理事件产生的类可以接受事件监听者的登记,一有事件发生,相关的 监听类便会被调用。这里,监听者是以接口型登记的。就是说,处理事件产生的类只依赖于监听接口。

设计成只依赖于接口因此完全是参数依赖的类实际上是非常非常稀少的,但正是这些类形成了 组件的基础,因为它们可以在不同的地方和其他的类相组合而没有副作用。在极端情况下,如果 所有的类都是参数依赖,并且组合可以抽象成一个层次结构,那么这就是面向连接的组合了 (Connection-oriented)。

第三种组合方式是基于相关环境(Contextual )的组合,其代表产品是EJB容器与.NET的Enterprise Service Context。与面向连接的组合不同,这种基于相关环境的组合是隐性的、非对称的。 容器中的组件能受益于容器提供的服务(如transaction管理)和容器给组件呈现出的对外部世界 的抽象(如能看什么,不能看什么)。

Szyperski还从概念上对容器和平台(platform)进行了对比。有些平台实际是具备了容器的特性。 例如对进程严格隔离的操作系统可以看作是进程容器。两个运行于不同进程的程序只能通过操作 系统所提供和控制的机制才能进行通讯。而另外一些操作系统则不具备这个特性,因为它不能 拦截进程间的通讯请求。

对这三种组合方式,Szyperski分别用了三个比喻来描述:Object组合就象是连接到Internet hub, 不用去管下面的网络如何连线与切换;连接组合就象是把不同的电子器件用导线互相连接起来; 而相关环境组合就象是手机通过基站来通讯一样。

Sep 2002 Clemens Szyperski: Back to Universe

这是上一期专栏的继续。作者指出,选择组合方式时要考虑的一个重要指标是组合的稳定性。 所谓稳定性就是在组成部分发生一定程度的变化时组合体仍然能正常工作。作者进一步对 三种组合方式的稳定性进行了总结:

  1. 对象型组合:这种组合是非常脆弱的,这是由于对象之间可能存在潜在的相互依赖,特别是被 继承的基类发生变化时。
  2. 容器型组合:这种组合是很稳定的,这是由于容器起了很强的隔离作用,防止了随意继承这样 的情况,同时容器也可以用一定的协议对组件发生变化时进行控制。
  3. 连接型组合:这种组合也可以是稳定的,因为在这种组合中没有随意继承。同时,需要的接口 (required interface)同相连的供给的接口(provided interface)之间可以有协议来处理 版本变化的情况。 (作者是用"Objects with explicitly required interfaces"来描述第三类组合,但从 上期对连接型组合的介绍来看,似乎与连接型组合非常吻合。

另外,作者还引入了"数据驱动型"组合,这是指系统根据不同的数据流而动态地决定不同组件 的组合。如在一个面向消息(message-oriented)的系统中,接收到的消息类型的不同可以起动 不同的组件。即是说,组合是数据流的结果。

对于显型组合(Explicit composition,似乎主要是指连接型组合),容器型组合和数据驱动型 组合,作者也简要地列出了一些各自的相关产品/工具。

  1. 显型组合: ADLs (architecture description languages), BML (IBM Research's bean markup language) and visual bean assembly tools (like Sun's BeanBox);
  2. 容器型组合:COM apartments, MTS contexts, EJB containers, COM+ contexts, CLR AppDomains and contexts;
  3. 数据驱动型组合:工作流引擎(workflow engine)和消息队列系统(message queuing system)。

最后,作者也认为,对不同组合方式的认识,包括它们的局限性以及在选择时的取舍平衡都还远远 不够,需要更多的理论研究与开发实践来丰富这方面的知识。

修改记录

  • 2006年12月17日:发表于SoftwarePractitioner.org。
 
[首页]   [作文]   [翻译]   [随笔]   [本站]   [English]
 
Creative Commons License
Except where otherwise noted, this site is licensed
under a Creative Commons Attribution-NonCommercial 2.5 License
.