第3章 绑定模型和实现

当我走进办公室,首先映入眼帘的是打印在数张大纸上的完整类图,它铺满了一整面墙。这是我进入某个项目的第一天,在此之前,聪明的项目组成员花费了几个月的时间进行仔细的研究并且开发出了上面这幅详尽的领域模型。该模型中的对象一般都与3~4个其他对象有着复杂的关联,而这张关联网几乎没有边界。在这方面,分析人员忠实地反映了领域自身的性质。

尽管这张墙面大小的图让人吃不消,但是它所表现的模型确实捕获了一些知识。经过一段时间的研究,我确实从中学到了不少知识(但是这种学习很难找到头绪,更像是在随意地浏览网页)。然而对类图的研究并不能让我深入地了解该应用程序的代码和设计,这让我备感困扰。

当开发人员开始实现应用程序时,他们很快就发现,尽管分析人员说得头头是道,他们依然无法将这种错综复杂的关系转换成可存储、可检索的且具有事务完整性的单元。请注意,该项目使用的是对象数据库,也就是说开发人员根本不用考虑对象—关系表映射这种难题。从根本上说,该模型无法为应用程序的实现提供帮助。

由于模型是“正确的”,这是经过技术分析人员和业务专家大量协作才得到的结果,因此开发人员得出这样的结论:无法把基于概念的对象作为设计的基础。于是他们开始进行专门针对程序开发的设计。他们的设计确实用了一些原有模型中类和属性的名称进行数据存储,但这种设计并不是建立在任何已有模型的基础上的。

这个项目虽然建立了领域模型,但是如果模型不能直接帮助开发可运行的软件,那么这种纸上谈兵的模型又有什么意义呢?

几年后,我在一个完全不同的项目中又看到了完全相同的结果。该项目要用Java实现新设计,并用新设计替换现存的C++应用程序。老版本的程序根本没有进行对象建模,仅仅是把功能堆积在一起。老版本的设计(如果有的话)就是在已有代码的基础上一个一个地堆积新功能,完全没有任何泛化或抽象的迹象。

奇怪的是,这两种开发流程所完成的最终产品却非常相似!它们都充斥了大量功能,难于理解,难以维护。尽管有些程序实现是比较直观的,但是仅通过阅读代码依然无法深入了解该系统的目的所在。除了精心设计的数据结构之外,这两种开发流程都没有利用其开发环境中的面向对象的设计范式。

模型种类繁多,作用各有不同,即使是那些仅用于软件开发项目的模型也是如此。领域驱动设计要求模型不仅能够指导早期的分析工作,还应该成为设计的基础。这种设计方法对于代码的编写有着重要的意义。不太明显的一点就是:领域驱动设计要求一种不同的建模方法……

3.1 模式:MODEL-DRIVEN DESIGN

第3章 绑定模型和实现 - 图1 过去用来计算星体位置的星盘[5]是天空模型的机械实现

中世纪的星象电脑

星盘是由古希腊的天文学家发明的;在中世纪,伊斯兰科学家又对它进行了改进。星盘上可旋转的铜环(又称“网环”)代表各恒星在天球上的位置。刻有当地地平坐标系的盘面是可换的,它代表的是不同纬度的星空景象。在星盘盘面上旋转网环,可以计算出全年任何时刻的天体位置。反过来,如果知道太阳或某个恒星的位置,也可以用星盘算出时间。星盘以机械化的方式实现了代表星空的面向对象模型。

严格按照基础模型来编写代码,能够使代码更好地表达设计含义,并且使模型与实际的系统相契合。

那些压根儿就没有领域模型的项目,仅仅通过编写代码来实现一个又一个的功能,它们无法利用前两章所讨论的知识消化和沟通所带来的好处。如果涉及复杂的领域就会使项目举步维艰。

另一方面,许多复杂项目确实在尝试使用某种形式的领域模型,但是并没有把代码的编写与模型紧密联系起来。这些项目所设计的模型,在项目初期还可能用来做一些探索工作,但是随着项目的进展,这些模型与项目渐行渐远,甚至还会起误导作用。所有在模型上花费的精力都无法保证程序设计的正确性,因为模型和设计是不同的。

模型和程序设计之间的联系可能在很多情况下被破坏,但是二者的这种分离往往是有意而为之的。很多设计方法都提倡使用完全脱离于程序设计的分析模型,并且通常这二者是由不同的人员开发的。之所以称其为分析模型,是因为它是对业务领域进行分析的结果,它在组织业务领域中的概念时,完全不去考虑自己在软件系统中将会起到的作用。分析模型仅仅是理解工具,人们认为把它与程序实现联系在一起无异于搅浑一池清水。随后的程序设计与分析模型之间可能仅仅保持一种松散的对应关系。在创建分析模型时并没有考虑程序设计的问题,因此分析模型很有可能无法满足程序设计的需求。

这种分析中会有一些知识消化的过程,但是在编码开始后,如果开发人员不得不重新对设计进行抽象,那么大部分的领域知识就会被丢弃。如此一来,就不能保证在新的程序设计中还能保留或者重现分析人员所获得的并且嵌入在模型中的领域知识。到了这一步,要维护程序设计和松散连接的模型之间的对应关系就很不合算了。

纯粹的分析模型甚至在实现理解领域这一主要目的方面也捉襟见肘,因为在程序设计和实现过程中总是会发现一些关键的知识点,而细节问题则会出人意料地层出不穷。前期模型可能会深入研究一些不相关的问题,反而忽略了一些重要的方面。而且它对于其他问题的描述也可能对应用程序没有任何帮助。最后的结果就是:编码工作一开始,纯粹的分析模型就被抛到一边,大部分的模型都需要重新设计。即便是重新设计,如果开发人员认为分析与程序开发毫不相关,那么建模过程就不会那么规范。而如果项目经理也这么认为,那么开发团队可能没有足够的机会与领域专家进行交流。

无论是什么原因,软件的设计如果缺乏概念,那么软件充其量不过是一种机械化的产品——只实现有用的功能却无法解释操作的原因。

如果整个程序设计或者其核心部分没有与领域模型相对应,那么这个模型就是没有价值的,软件的正确性也值得怀疑。同时,模型和设计功能之间过于复杂的对应关系也是难于理解的,在实际项目中,当设计改变时也无法维护这种关系。若分析与和设计之间产生严重分歧,那么在分析和设计活动中所获得的知识就无法彼此共享。

分析工作一定要抓住领域内的基础概念,并且用易于理解和易于表达的方式描述出来。设计工作则需要指定一套可以由项目中使用的编程工具创建的组件,使项目可以在目标部署环境中高效运行,并且能够正确解决应用程序所遇到的问题。

MODEL-DRIVEN DESIGN(模型驱动设计)不再将分析模型和程序设计分离开,而是寻求一种能够满足这两方面需求的单一模型。不考虑纯粹的技术问题,程序设计中的每个对象都反映了模型中所描述的相应概念。这就要求我们以更高的标准来选择模型,因为它必须同时满足两种完全不同的目标。

有很多方法可以对领域进行抽象,也有很多种设计可以解决应用程序的问题。因此,绑定模型和程序设计是切实可行的。但是这种绑定不能够因为技术考虑而削弱分析的功能,我们也不能接受那些只反映了领域概念却舍弃了软件设计原则的拙劣设计。模型和设计的绑定需要的是在分析和程序设计阶段都能发挥良好作用的模型。如果模型对于程序的实现来说显得不太实用时,我们必须重新设计它。而如果模型无法忠实地描述领域的关键概念,也必须重新设计它。这样,建模和程序设计就结合为一个统一的迭代开发过程。

将领域模型和程序设计紧密联系在一起绝对是必要的,这也使得在众多可选模型中选择最适用的模型时,又多了一条选择标准。它要求我们认真思考,并且通常会经过多次反复修改和重新构建的过程,但是通过这样的过程可以得到与设计关联的模型。

因此:

软件系统各个部分的设计应该忠实地反映领域模型,以便体现出这二者之间的明确对应关系。我们应该反复检查并修改模型,以便软件可以更加自然地实现模型,即使想让模型反映出更深层次的领域概念时也应如此。我们需要的模型不但应该满足这两种需求,还应该能够支持健壮的UBIQUITOUS LANGUAGE(通用语言)。

从模型中获取用于程序设计和基本职责分配的术语。让程序代码成为模型的表达,代码的改变可能会是模型的改变。而其影响势必要波及接下来相应的项目活动。

完全依赖模型的实现通常需要支持建模范式的软件开发工具和语言,比如面向对象的编程。

有时,不同的子系统会有不同的模型(参见第14章),但是从需求分析到代码编写的整个开发过程中,软件系统的每一部分只能对应一个模型。

单一模型能够减少出错的概率,因为程序设计直接来源于经过仔细考虑而创建的模型。程序设计,甚至是代码本身,都与模型密不可分。

要想创建出能够抓住主要问题并且帮助程序设计的单一模型并没有说的那么容易。我们不可能随手抓个模型就把它转化成可使用的设计。只有经过精心设计的模型才能促成切实可行的实现。想要使代码有效地描述模型就需要用到程序设计和实现的技巧(参见第二部分)。知识消化人员需要研究模型的各个选项,并将它们细化为实用的软件元素。软件开发于是就成了一个不断精化模型、设计和代码的统一的迭代过程(参见第三部分)。

3.2 建模范式和工具支持

为了使MODEL-DRIVEN DESIGN发挥作用,一定要在可控范围内严格保证模型与设计之间的一致性。要实现这种严格的一致性,必须要运用由软件工具支持的建模范式,它可以在程序中直接创建模型中的对应概念。

第3章 绑定模型和实现 - 图2 图3-1

面向对象编程之所以功能强大,是因为它基于建模范式,并且为模型构造提供了实现方式。如图3-1所示。从程序员的角度来看,对象真实存在于内存中,它们与其他对象相互联系,它们被组织成类,并且通过消息传递来完成相应的行为。许多开发人员只是得益于对象的技术能力——用其组织程序代码,只有用代码表达模型概念时,对象设计的真正突破之处才彰显出来。Java和许多其他工具都允许创建直接反映概念对象模型的对象和关系。

Prolog语言并不像面向对象语言那样被广泛使用,但是它却非常适合MODEL-DRIVEN DESIGN。在MODEL-DRIVEN DESIGN中,建模范式是逻辑的,而模型则是一组逻辑规则以及这些规则所操作的事实。

像C这样的语言并不适用于MODEL-DRIVEN DESIGN,因为没有适用于纯粹过程语言的建模范式。对过程语言而言,程序员要告诉电脑一系列要执行的操作步骤。尽管程序员也会考虑到领域中的概念,但是程序本身仅仅是一组对数据进行的技术操作。最终程序可能是实用的,但是它并没有包含太多的意义。过程语言通常支持复杂的数据类型,这些数据类型一开始就能很自然地对应到领域中的概念上,但是它们也只是被组织在一起的数据,并不能描述领域的活跃方面。因此,用过程语言编写的软件具有复杂的函数,这些函数基于预先制定的执行路径连接在一起,而不是通过领域模型中的概念联系进行连接的。

在我还没听说面向对象编程的时候,我是通过Fortran程序来实现数学模型的,这也正是Fortran所擅长的领域。数学函数是这种模型中主要的概念组件,也是Fortran语言能够清晰描述的。即便如此,对于超越函数的更高层次的意义,Fortran就毫无办法了。大部分非数学领域都不适合用过程语言来进行MODEL-DRIVEN DESIGN,因为这些领域无法被抽象成数学函数或者过程中的操作步骤。

面向对象设计是目前大多数项目所使用的建模范式,也是本书中使用的主要方法。

示例 从过程设计到MODEL-DRIVEN DESIGN

第1章讲过,我们可以把PCB看作是连接各种电路元件引脚的导体(称为net)集合。电路板上通常都会有成千上万个net。有一种叫做PCB布线工具的专用软件,能够为所有net安排物理布线,而不会使它们相互交叉或干扰。它的实现方法就是根据设计者规定的大量限制条件来限制布线方式以及优化路径选择。尽管PCB布线工具已经非常先进了,但是它仍然有一些缺陷。

其中一个问题是这些数以千计的net都拥有各自的布线规则,而PCB工程师会根据net自身的性质将其分组,同组的net共用相同的规则。比如,有些net构成了总线。如图3-2所示。

第3章 绑定模型和实现 - 图3 图3-2 net构成总线的示意图

工程师每次用8个、16个或者256个net组合成总线,这样布线工作就更易于管理了,不但提高了效率也减少了错误。问题是布线工具中没有类似于总线这样的概念。布线规则不得不应用于成千上万个net,一次处理一个net。

呆板的设计

走投无路的工程师只能用变通方法绕过布线工具的限制,编写脚本来分析布线工具的数据文件,然后将规则直接插入到文件中,从而一次性将规则应用于整个总线。

布线工具在net列表文件中存储每个电路连接,如下所示:

第3章 绑定模型和实现 - 图4

而布线规则被存储在类似于下面格式的文件中:

第3章 绑定模型和实现 - 图5

第3章 绑定模型和实现 - 图6

工程师为net制定严格的命名约定,这样将数据文件的内容按照字母排序,就可以使构成同一条总线的所有net都排列在一起。然后他们编写的脚本就可以解析该文件并且基于总线来修改每个net。用来解析、处理和写入文件的实际代码太过冗长晦涩,对解释这个例子没有什么帮助,所以我在下面只列出了这个处理过程中要执行的步骤。

(1) 按照net名称将net列表文件排序。

(2) 逐行读取文件,寻找以总线名称开头的第一行数据。

(3) 解析名称匹配的每一行,获取每行中net的名称。

(4) 将net名称和规则文本附加到规则文件的末尾。

(5) 从第(3)步起重复执行,直到没有匹配该总线名称的行。

总线规则的输入文件采用如下的格式:

第3章 绑定模型和实现 - 图7

经过处理后,输出的是添加了net规则的文件,如下所示:

第3章 绑定模型和实现 - 图8

我猜想第一个编写这个脚本的人只有这种简单的需求,如果情况确实如此,那么使用这样的脚本是完全合理的。但实际情况是,已经存在成堆的脚本了。当然,可以通过重构来共用排序及字符串匹配之类的函数,而且如果脚本语言支持通过函数调用来封装细节,那么这些脚本看上去会和上面给出的步骤差不多。但是,它们依然只是对文件进行操作。文件格式一有不同(确实有几种)就需要重新编写一套程序,即便是总线分组以及为其分配规则的概念是相同的。如果你想要实现更多的功能和交互,就得下血本。

脚本的编写者试图在布线工具的领域模型中补充“总线”这个概念,他们的脚本通过排序和字符串匹配来判断总线的存在,却没有明确地定义总线概念。

MODEL-DRIVEN DESIGN

前面我们已经描述了领域专家思考问题时所使用的概念。现在需要将这些概念组织成模型,作为软件开发的基础。

第3章 绑定模型和实现 - 图9 图3-3 用来高效指定布线规则的类图

用面向对象的语言实现上图的这些对象后,核心功能的实现会变得轻而易举。

方法assignRule可以在抽象类AbstractNet中实现。而类Net中的方法assignedRules则分配了其自身的规则以及类Bus的规则。

第3章 绑定模型和实现 - 图10

当然,程序还需要大量的支持代码,但上面的代码片段已经呈现了脚本的基本功能。

这个程序还需要导入/导出逻辑,我们可以将其封装成一些简单的服务。

第3章 绑定模型和实现 - 图11

我们还需要几个工具类:

第3章 绑定模型和实现 - 图12

现在,启动应用程序,用导入数据来初始化Net和Bus仓库。

第3章 绑定模型和实现 - 图13

上面提到的服务和仓库都可以进行单元测试。更重要的是还可以测试核心领域逻辑。下面是对最核心的行为进行的单元测试(采用了JUnit测试框架):

第3章 绑定模型和实现 - 图14

程序应该有一个交互式的用户界面,可以列出所有总线,让用户逐个指定规则;或者可以向后兼容,从规则文件中读取规则。采用外观(façade)模式可以更容易地访问这些接口,如下代码是对应于上面测试的实现代码:

第3章 绑定模型和实现 - 图15

最后一行代码:

第3章 绑定模型和实现 - 图16

(这项服务调用每个Net的assignedRules()方法,然后将所有规则完全写入规则文件。)

当然,如果只有一种操作(就像这个例子一样),那么基于脚本的处理方式可能也同样实用。但实际上,通常会需要20个甚至更多的操作。MODEL-DRIVEN DESIGN易于扩展,能够为规则的组合设臵限制条件,还能提供其他的一些增强功能。

MODEL-DRIVEN DESIGN也为测试提供了方便。它的组件都具有定义完善的接口,可以进行单元测试。而测试脚本程序的唯一方法就是基于文件进行端到端的比较。

记住,这样的设计不是一蹴而就的。我们需要反复研究领域知识,不断重构模型,才能将领域中重要的概念提炼成简单而清晰的模型。

3.3 揭示主旨:为什么模型对用户至关重要

从理论上讲,也许你可以向用户展示任何一种系统视图,而不管底层如何实现。但实际上,系统上下层结构的不匹配轻则导致误解,重则产生bug。让我们看一个非常简单的例子——微软IE浏览器的早期版中,网站书签功能对应的模型是如何误导用户的[6]

IE浏览器用户会认为“收藏夹”是存储网站名称的列表,网站名称在不同的会话中是保持不变的。但是系统实现却将收藏夹中的书签当作一个包含URL的文件,并将文件名称存储在收藏夹列表中。这样做的问题是,如果网页标题含有Windows系统文件名不能接受的非法字符,就会出现错误。假如用户想要收藏某页面并将其命名为:“Laziness: The Secret to Happiness”(懒惰:幸福的秘密),就会弹出一个错误信息:“文件名不能包含下列任何字符:\ / : * ? " < > |”。用户会奇怪文件名是指什么。另一方面,如果网页标题已经包含非法字符,IE浏览器则会悄悄地把字符删除。这种数据丢失在这种情况下也许危害不大,但绝不是用户所期望的。在大多数应用中,程序悄悄地修改数据是不能接受的。

MODEL-DRIVEN DESIGN要求只使用一个模型(在任何一个上下文中都是如此,第14章会讨论这一点)。大部分的设计建议和例子都只针对将分析模型和设计模型分离的问题,但是这里的问题涉及了另外一对不同的模型:用户模型和设计/实现模型。

当然,大多数情况下,没有经过处理的领域模型视图肯定不便于用户使用。但是在用户界面中出现与领域模型不同的“影像”将会使用户产生迷惑,除非这个“影像”完美无缺。如果网站收藏夹实际上只是快捷方式文件的集合,那么应该将这一事实告诉用户,还应该删除之前那个起误导作用的模型。这样不但能使收藏夹的功能更加清晰,用户还可以利用自己所知道的文件系统的知识来对网站收藏夹进行操作。比如,用户可以用资源管理器来重新组织已收藏的文件,而不是用浏览器内臵的拙劣工具。而电脑高手还能够灵活地在文件系统的任何位臵存储网页快捷方式。仅仅通过删除起误导作用的多余模型就可以让应用程序的功能更加强大且清晰。如果程序员认为原有模型足够好,那么为什么还要让用户学习新模型呢?

此外,如果以不同的方式来存储收藏夹,比如将其存储在一个数据文件中,这样收藏文件就可以有自己的规则了。这些规则很可能是应用于网页的命名规则。这又是一个单一模型。这个模型告诉用户所有关于网站的命名规则都适用于网站收藏夹。

如果程序设计基于一个能够反映出用户和领域专家所关心的基本问题的模型,那么与其他设计方式相比,这种设计可以将其主旨更明确地展示给用户。让用户了解模型,将使他们有更多机会挖掘软件的潜能,也能使软件的行为合乎情理、前后一致。

3.4 模式:HANDS-ON MODELER

人们总是把软件开发比喻成制造业。这个比喻的一个推论是:经验丰富的工程师做设计工作,而技能水平较低的劳动力负责组装产品。这种做法使许多项目陷入困境,原因很简单——软件开发就是设计。虽然开发团队中的每个成员都有自己的职责,但是将分析、建模、设计和编程工作过度分离会对MODEL-DRIVEN DESIGN产生不良影响。

我曾经在一个项目中负责协调不同的应用程序开发团队,帮助开发可以驱动程序设计的领域模型。但是管理层认为建模人员就应该只负责建模工作,编写代码就是在浪费这种技能,于是他们不准我编写代码或者与程序员讨论细节问题。

开始项目进展的还算顺利。我和领域专家以及各团队的开发负责人共同工作,消化领域知识并提炼出了一个不错的核心模型。但是该模型却从来没有派上用场,原因有两个。

其一,模型的一些意图在其传递过程中丢失了。模型的整体效果受细节的影响很大(这将在第二部分和第三部分讨论),这些细节问题并不是总能在UML图或者一般讨论中遇到的。如果我能撸起袖子,直接与开发人员共同工作,提供一些参考代码和近距离的技术支持,那么他们也许能够理解模型中的抽象概念并据此进行开发。

第二个原因是模型与程序实现及技术互相影响,而我无法直接获得这种反馈。例如,程序实现过程中发现模型的某部分在我们的技术平台上的工作效率极低,但是经过几个月的时间,我才一点一点获得了关于这个问题的全部信息。其实只需较少的改动就能解决这个问题,但是几个月过去了,改不改已经不重要了。因为开发人员已经自行编写出了可以运行的软件——完全脱离了模型的设计,在那些还在使用模型的地方,也仅仅是把它当作纯粹的数据结构。开发人员不分好坏地把模型全盘否定,但是他们又有什么办法呢?他们再也不愿意冒险任由呆在象牙塔里的架构师摆布了。

与其他项目一样,这个项目的初始环境倾向于不让建模人员参与太多的程序实现。对于该项目所使用的大部分技术,我都有着大量的实践经验。在做建模工作之前,我甚至曾经在同类项目中领导过一个小的开发团队,所以我对项目开发过程和编程环境非常熟悉。但是如果不让建模人员参与程序实现,我就是有这些经历也无法有效地工作。

如果编写代码的人员认为自己没必要对模型负责,或者不知道如何让模型为应用程序服务,那么这个模型就和程序没有任何关联。如果开发人员没有意识到改变代码就意味着改变模型,那么他们对程序的重构不但不会增强模型的作用,反而还会削弱它的效果。同样,如果建模人员不参与到程序实现的过程中,那么对程序实现的约束就没有切身的感受,即使有,也会很快忘记。MODEL-DRIVEN DESIGN的两个基本要素(即模型要支持有效的实现并抽象出关键的领域知识)已经失去了一个,最终模型将变得不再实用。最后一点,如果分工阻断了设计人员与开发人员之间的协作,使他们无法转达实现MODEL-DRIVEN DESIGN的种种细节,那么经验丰富的设计人员则不能将自己的知识和技术传递给开发人员。

HANDS-ON MODELER(亲身实践的建模者)并不意味着团队成员不能有自己的专业角色。包括极限编程在内的每一种敏捷过程都会给团队成员分配角色,其他非正式的专业角色也会自然而然地产生。但是如果把MODEL-DRIVEN DESIGN中密切相关的建模和实现这两个过程分离开,则会产生问题。

整体设计的有效性有几个非常敏感的影响因素——那就是细粒度的设计和实现决策的质量和一致性。在MODEL-DRIVEN DESIGN中,代码是模型的表达,改变某段代码就改变了相应的模型。程序员就是建模人员,无论他们是否喜欢。所以在开始项目时,应该让程序员完成出色的建模工作。

因此:

任何参与建模的技术人员,不管在项目中的主要职责是什么,都必须花时间了解代码。任何负责修改代码的人员则必须学会用代码来表达模型。每一个开发人员都必须不同程度地参与模型讨论并且与领域专家保持联系。参与不同工作的人都必须有意识地通过UBIQUITOUS LANGUAGE与接触代码的人及时交换关于模型的想法。

将建模和编程过程完全分离是行不通的,然而大型项目依然需要技术负责人来协调高层次的设计和建模,并帮助做出最困难或最关键的决策。本书的第四部分描述的就是这种决策,通过学习该部分内容可以激发灵感,找到更高效的方法来定义高级技术人员的角色和职责。

MODEL-DRIVEN DESIGN利用模型来为应用程序解决问题。项目组通过知识消化将大量杂乱无章的信息提炼成实用的模型。而MODEL-DRIVEN DESIGN将模型和程序实现过程紧密结合。UBIQUITOUS LANGUAGE则成为开发人员、领域专家和软件产品之间传递信息的渠道。

最终的软件产品能够在完全理解核心领域的基础上提供丰富的功能。

如上所述,MODEL-DRIVEN DESIGN的成功离不开详尽的设计决策。在下面几章中我们将会讲述这方面的内容。


[1].走查,walk through,原来是指一种非正式的代码评审活动,现在也广泛用于其他方面,一般是指一步步检查或分步讨论。——译者注

[2].卫语句,guard clause,指起保护作用的语句。——译者注

[3].STRATEGY一般是指定义一组算法,将每个算法都封装起来,并且使它们之间可以互换。策略模式的优点是软件可以由许多可替换的部分组成,各个部分之间是弱连接的关系,这样软件具有更强的可扩展性、可维护性和可重用性。作者在这里提到策略模式,是指将每个规则当成一个算法。——译者注

[4].清关即结关,习惯上又称通关,是指进口货物、出口货物和转运货物进出一国海关或国境时必须向海关申报,办理海关规定的各项手续,履行各项法规规定的义务。——译者注

[5].一种中世纪的仪器,曾用来测量太阳或其他天体的位臵。——译者注

[6].Brian Marick曾向我提及这个示例。