共计 10846 个字符,预计需要花费 28 分钟才能阅读完成。
构建计算机系统并非易事。随着系统复杂性的增大,构建相应软件的难度将呈指数增大。
同其他行业一样,我们只有在不断的学习中进步,从成功经验中学习,从失败教训中学习,才有望克服这些困难。
这本书的内容就是这样一些“学习”经验。
只有通过模式的总结和学习,才能更有效地与他人进行交流。
—选自《企业应用架构模式》
0.1 架构
软件业的人乐于做这样的事——找一些词汇,并把它们引申到大量微妙而又互相矛盾的含义。一个最大的受害者就是“架构”(architecture)这个词。我个人对“架构”的感觉是,它是一个让人印象深刻的词,主要用来表示一些非常重要的东西。当然,我也会小心,不让这些对“系统结构”的“不恭之词”,影响到读者对本书的兴趣。
很多人都试图给“架构”下定义,而这些定义本身却很难统一。能够统一的内容有两点:一点是“最高层次的系统分解”;另一点是“系统中不易改变的决定”。越来越多的人发现:表述一个系统架构的方法不只一种;一个系统中也可能有很多种不同的架构,而且,对于什么在架构上意义重大的看法也会随着系统的生命周期变化。
Ralph Johnson 经常在邮件列表上发帖,并提出一些令人关注的见解。就在我完成本书初稿的同时,他又发表了一些关于“架构”的观点。他认为,架构是一种主观上的东西,是专家级项目开发人员对系统设计的一些可共享的理解。一般地,这种可共享的理解表现为系统中主要的组成部分以及这些组成间的交互关系。它还包括一些决定,开发者们希望这些决定能及早做出,因为在开发者看来它们是难以改变的。架构的主观性也来源于此——如果你发现某些决定并不像你想象的那么难以改变,那么它就不再与架构相关。到了最后,架构自然就浓缩成一些重要的东西,不论这些东西是什么。
在本书中,我提出一些自己的理解,涉及企业应用主要组成部分和我希望能尽早做出的决定。在这些架构模式中,我最欣赏的就是“层次”,将在第 1 章中进行详细介绍。全书实际上就是关于如何将企业应用组织成不同的层次,以及这些层次之间如何协同工作。大多数重要的企业应用都是按照某种形式的层次分层设计的;当然,在某些情况下,别的设计方式(如管道方式、过滤器方式等)也有它们自己的价值。在本书中我们将不会讨论这些方式,而把注意力集中在层次方式上,因为它是应用最广的设计方式。
本书中的一些模式毫无疑问是关于架构的,它们表示了企业应用各主要组成部分间的重要决定;另外一些模式是关于设计的,有助于架构的实现。我没有刻意区分这两类模式,因为正如我们前面讨论的,是否与架构相关往往带有主观性。
0.2 企业应用
编写计算机软件的人很多,我们通常把这些活动都称为软件开发。但是软件的种类是不同的,每种软件都有自身的挑战性和复杂性。我是在与几个从事电信软件开发的朋友交谈后,意识到这个问题的。企业应用在某些方面要比电信软件简单得多——多线程问题没有那么困难,无需关注硬件设备与软件的集成。但是,在某些方面,企业应用又比电信软件复杂得多——企业应用一般都涉及到大量复杂数据,而且必须处理很多“不合逻辑”的业务规则。虽然有些模式是适合所有软件的,但是大多数模式都还只适合某些特定的领域和分支。
我的工作主要是关于企业应用的,因此,这里所谈及的模式也都是关于企业应用的。(企业应用还有一些其他的说法,如“信息系统”或更早期的“数据处理”。)那么,这里的“企业应用”具体指的是什么呢?我无法给出一个精确的定义,但是我可以罗列一些个人的理解。
先举几个例子。企业应用包括工资单、患者记录、发货跟踪、成本分析、信誉评估、保险、供应链、记账、客户服务以及外币交易等。企业应用不包括车辆加油、文字处理、电梯控制、化工厂控制器、电话交换机、操作系统、编译器以及电子游戏等。
企业应用一般都涉及到持久化数据。数据必须持久化是因为程序的多次运行都需要用到它们——实际上,有些数据需要持久化若干年。在此期间,操作这些数据的程序往往会有很多变化。这些数据的生命周期往往比最初生成它们的那些硬件、操作系统和编译器还要长。在此期间,数据本身的结构一般也会被扩展,使得它在不影响已有信息的基础上,还能表示更多新信息。即使是有根本性的变化发生,或公司安装了一套全新的软件,这些数据也必须被“迁移”到这些全新的应用上。
企业应用一般都涉及到大量数据——一个中等规模的系统往往都包含 1GB 以上的数据,这些数据是以百万条记录的方式存在的。巨大的数据量导致数据的管理成为系统的主要工作。早期的系统使用的是索引文件系统,如 IBM 的 VSAM 和 ISAM。现代的系统往往采用数据库,绝大多数是关系型数据库。数据库的设计和演化已使其本身成为新的技术领域。
企业应用一般还涉及到很多人同时访问数据。对于很多系统来说,人数可能在 100 人以下,但是对于一些基于 Web 的系统,人数会呈指数级增长。要确保这些人都能够正确地访问数据,就一定会存在这样或那样的问题。即使人数没有那么多,要确保两个人在同时操作同一数据项时不出现错误,也是存在问题的。事务管理工具可以处理这个问题,但是它通常无法做到对应用开发者透明。
企业应用还涉及到大量操作数据的用户界面屏幕。有几百个用户界面是不足为奇的。用户使用频率的差异很大,他们也经常没什么技术背景。因此,为了不同的使用目的,数据需要很多种表现形式。系统一般都有很多批处理过程,当专注于强调用户交互的用例时,这些批处理过程很容易被忽视。
企业应用很少独立存在,通常需要与散布在企业周围的其他企业应用集成。这些各式各样的系统是在不同时期,采用不同技术构建的,甚至连协作机制都不同:COBOL 数据文件、CORBA 系统或是消息系统。企业经常希望能用一种统一的通信技术来集成所有系统。当然,每次这样的集成工作几乎都很难真正实现,所有留下来的就是一个个风格各异的集成环境。当商业用户需要同其业务伙伴进行应用集成时,情况就更糟糕。
即使是某个企业统一了集成技术,它们也还是会遇到业务过程中的差异以及数据中概念的不一致性。一个部分可能认为客户是当前签有协议的人;而另外一个部门可能还要将那些以前有合同,但现在已经没有了的人计算在内。再有,一个部门可能只关心产品销售而不关心服务销售。粗看起来,这些问题似乎容易解决,但是,一旦几百个记录中的每个字段都有可能存在着细微差别,问题的规模就会形成不小的挑战——就算唯一知道这些字段之间差别的员工还在公司任职(当然,也许他在你察觉到之前就早已辞职不干了)。这样,数据就必须被不停地读取、合并、然后写成各种不同语法和语义的格式。
再接下来的问题是由“业务逻辑”带来的。我认为“业务逻辑”这个词很滑稽,因为很难找出什么东西比“业务逻辑”更加没有逻辑。当我们构建一个操作系统时,总是尽可能地使得系统中的各种事物符合逻辑。而业务逻辑生来就是那样的,没有相当的行政努力,不要想改变它,当然,它们都有自己的理由。你必须面对很多奇怪的条件。而且这些条件相互作用的方式也非常怪异。比如,某个销售人员为了签下其客户几百万美元的一张单,可能会在商务谈判中与对方达成协议,将该项目的年度到账时间推迟两天,因为这样才能与该客户的账务周期相吻合。成千上万的这类“一次特殊情况”最终导致了复杂的业务“无逻辑”,使得商业软件开发那么困难。在这种情况下,必须尽量将这些业务逻辑组织成有效的方式,因为我们可以确定的是,这些“逻辑”一定会随着时间不断变化。
对于一些人来说,“企业应用”这个词指的是大型系统。但是 需要注意的是,并不是所有的企业应用都是大型的,尽管它们可能都为企业提供巨大的价值。很多人认为,由于小型系统的规模不大,可以不用太注意它们,而且在某种程度上,这种观点能够带来一定的成本节约。如果一个小型系统失败了,相对于大型系统的失败,这种失败就不会显得那么起眼了。但是,我认为这种思想没有对小型项目的累积作用给予足够的重视。试想,如果在小型项目上能够进行某些改善措施,那么一旦这些改善措施被成功运用于大型项目,它带来的效果就会非常大。实际上,最好是通过简化架构和过程,将一个大型项目简化成小型项目。
0.3 企业应用的种类
在我们讨论如何设计企业应用以及使用哪些模式之前,明确这样一个观点是非常重要的,即企业应用是多种多样的,不同的问题将导致不同的处理方法。如果有人说“总是这样做”的时候,就应该敲响警钟了。我认为,设计中最具挑战性(也是我最感兴趣)的地方就是了解有哪些候选的设计方法以及各种不同设计方法之间的优劣比较。进行选择的控件很大,但我在这里只选三个方面。
考虑一个 B2C(Business to Customer)的网上零售商:人们通过浏览器浏览,通过购物车购买商品。通过购物车购买商品。这样一个系统必须能够应付大量的客户,因此,其解决方案不但要考虑到资源利用的有效性,还要考虑到系统的可伸缩性,以便在用户规模增大时能够通过增加硬件的办法加以解决。该系统的业务逻辑可以非常简单:获取订单,进行简单的价格计算和发货计算,给出发货信息。我们希望任何人都能够访问该系统,因此用户界面可以选用通用的 Web 表现方式,以支持各种不同的浏览器。数据源包括用来存放订单的数据库,还可能包括某种与库存系统的通信交流,以便获得商品的可用性信息和发货信息。
再考虑一个租约合同自动处理系统。在某些方面,这样的系统比起前面介绍的 B2C 系统要简单,因为它的用户数很少(在特定时间内不会超过 100 个),但是它的业务逻辑却比较复杂。计算每个租约的月供,处理如提早解约和延迟付款这样的事件,签订合同时验证各种数据,这些都是非常复杂的任务,因为租约领域的许多竞争都是以过去的交易为基础稍加变化而出现的。正是因为规则的随意性很大,才使得像这样一个复杂领域具有挑战性。
这样的系统在用户界面(UI)上也很复杂。这就要求 HTML 界面要能提供更丰富的功能和更复杂的屏幕,而这些要求往往是 HTML 界面目前无法达到的,需要更常规的胖客户界面。用户交互的复杂性还会带来事务行为的复杂性:签订租约可能要耗时 1~2 个小时,这期间用户要处于一个逻辑事务中。一个复杂的数据库设计方案中可能也会涉及到 200 多个表以及一些有关资产评估和计价的软件包。
第三个例子是一家小型公司使用的简单的“开支跟踪系统”。这个系统的用户很少,功能简单,通过 HTML 表现方式可以很容易实现,涉及的数据源表项也不多。尽管如此,开发这样的系统也不是没有挑战。一方面你必须快速地开发出它,另一方面你又必须为它以后可能的发展考虑;也许以后会为它增加赔偿校验的功能,也许它会被集成到工资系统中,也许还要增加关于税务的功能,也许要为公司的 CFO 生成汇总报表,也许会被集成到一个航空订票 Web Service 中,等等。如果在这个系统的开发中,也试图使用前面两个例子中的一些架构,可能会影响开发进度。如果一个系统会带来业务效益(如所有的企业应用应该的那样),则系统进度延误同样也是开销。如果现在不做决策又有可能影响系统未来的发展。但是,如果现在就考虑了这些灵活性但是考虑不得当,额外的复杂性又可能会影响到系统的发展,进一步延误系统部署,减少系统的效益。虽然这类系统很小,但是一个企业中往往有很多这样的系统,这些系统的架构不良性累积起来,后果将会非常可怕。
这三个企业应用的例子都有难点,而且难点各不相同。当然,也不可能有一个适合于三者的通用架构。选择架构时,必须很清楚地了解面临的问题,在理解的基础上再来选择合适的设计。本书中也没有一个通用的解决方案。实际上,很多模式仅仅是一些可选方案罢了。即使你选择了某种模式,也需要进一步根据面临的问题来修改模式。在构建企业应用时,你不思考是不行的。所有书本知识只是给你提供信息,作为你做决定的基础。
模式是这样,工具也同样如此。在系统开发时应该选取尽可能少的工具,同时也要注意,不同的工具擅长处理的方面也不同,切记不要用错了工具,否则只会事倍功半。
0.4 关于性能的考虑
很多架构的设计决策和性能有关。对于大多数与性能相关的问题,我的办法是首先建立系统,调试运行,然后通过基于测量的严格的优化过程来提高性能。但是,有一些架构上的决策对性能的影响,可能是后期优化难以弥补的。而且即使这种影响可以在后期很容易地弥补,参与这个项目的人们任然会从一开始就担心这些决策。
在这样的一本书中讨论性能通常很困难。这是因为“眼见为实”:所有那些关于性能的条条框框,不在你的具体系统中配置运行一下,是很难有说服力的。我也经常看到一些设计方案因为性能方面的考虑而被接受或拒绝,但是一旦有人在真实的设置环境中做一些测量,就会证明这些考虑是错误的。
本书将提出一些这方面的建议,包括尽量减少远程调用(它在很长时间内都被认为是优化性能的好建议)。尽管如此,还是建议读者在运用这些原则之前,在你的应用中具体试一试。同样,本书中的样例代码也有一些地方为了提高可读性而牺牲了效率。在你的系统中,需要自行决定是否进行优化。在做性能优化后,一定要与优化前进行测量对比,以确定真的得到了优化,否则,你可能只是破坏了代码的可读性。
还有一个很重要的推论:配置上的重大变化会使得某些性能优化失效。因此,在升级虚拟机、硬件、数据库或其他东西到新的版本时,必须重新确认性能优化工作的有效性。很多情况下,配置变更都会对性能优化有影响,有时候你真的会发现,以前为了提升性能做的优化,在新环境下居然影响性能。
关于性能的另一个问题是很多术语的使用不一致。最明显的例子就是“可伸缩性”(scalability),它可能有 6 - 7 种含义。下面我使用其中一些术语。
响应时间是系统完成一次外部请求处理所需要的时间。这些外部请求可能是用户交互行为,例如按下一个按钮,或是服务器 API 调用。
响应性不同于请求处理,它是系统响应请求的速度有多快。这个指标在许多系统里非常重要,因为对于一些系统而言,如果其响应性太慢,用户将难以忍受——尽管其响应时间可能不慢。如果在请求处理期间,系统一直处于等待状态,则系统的响应性和响应时间是相同的。然而,如果能够在处理真正完成之前就给用户一些信息表明系统已经接到请求,则响应性就会好一些。例如,在文件拷贝过程中,为用户提供一个“进度条”,将会提高用户界面的响应性,但并不会提高响应时间。
等待时间是获得系统任何形式响应的最小时间,即使应该做的工作并不存在。通常它是远程系统中的大问题。假设我们让程序什么都不做,只是调用返回即可,则如果在本机上运行程序,一般都会立即得到响应。但是,如果在远程计算机上运行程序,情况就不一样,往往需要数秒的时间才能得到响应。因为从发出请求到得到响应的数秒时间主要用于排除使信息在线路上传输的困难。作为应用开发者,我经常对等待时间无能为力。这也是为什么要尽量避免远程调用的原因。
吞吐率是给定时间内能够处理多大的请求量。如果考察的是文件拷贝,则吞吐率可以用每秒字节量来表示。对于企业应用来说,吞吐率通常用每秒事务数(tps)来度量。这种方法的一个问题是指标依赖于事务的复杂程度。对于特定系统的测试,应该选取普通的事务集合。
在这里,性能或指吞吐率,或者指响应时间,由用户自己决定。当通过某种优化技术后,使得系统的吞吐率提高了,但是响应时间下降了,这时就不好说系统的性能提高了,最好用更准确的术语表示。从用户角度而言,响应性往往比响应时间更重要,因此,为了提高响应性而损失一些响应时间或者吞吐率是值得的。
负载是关于系统当前负荷的表述,也许可以用当前有多少用户与系统相连来表示。负载有时也作为其他指标(如响应时间)的背景。因此,我们可以说:在 10 个用户的情况下,请求响应时间是 0.5 秒,在 20 个用户的情况下,请求响应时间是 2 秒。
负载敏感度是指响应时间随负载变化的程度。假设:系统 A 在 10~20 个用户的情况下,请求响应时间都是 0.5 秒;系统 B 在 10 个用户的情况下,请求响应时间是 0.2 秒,在 20 个用户的情况下,请求响应时间上升到 2 秒。此时,系统 A 的负载敏感度比系统 B 低;我们还可以使用术语衰减(degradation),称系统 B 衰减得比系统 A 快。
效率是性能除以资源。如果一个双 CPU 系统的性能是 30tps,另一个系统有 4 个同样的 CPU,性能是 40tps,则前者效率高于后者。
系统的容量是指最大有效负载或吞吐率的指标。它可以是一个绝对最大值或性能衰减至低于一个可接受的阈值之前的临界点。
可伸缩性度量的是向系统中增加资源(通常是硬件)对系统性能的影响。一个可伸缩性的系统允许在增加了硬件后,能够有性能上的合理提高。例如,为了使吞吐率提高一倍,要增加多少服务器等。垂直可伸缩性或称垂直延展,通常指提高单个服务器的性能,例如增加内存。水平可伸缩性或称水平延展,通常指增加服务器的数目。
问题是,设计决策对所有性能指标的作用并不相同。比如,某个服务器上运行着两个软件系统:Swordfish 的容量是 20tps,而 Camel 的容量是 40tps。哪一个的性能更高?哪一个的可伸缩性好?仅凭这些数据,我们无法回答关于可伸缩性的问题,我们只能说 Camel 系统在单片机上的效率更高。假设又增加了一台服务器后,我们发现:Swordfish 的容量是 35tps,Camel 的容量是 50tps。尽管 Camel 的容量仍然大于 Swordfish,但是后者在可伸缩性上却显得比前者更好。假设我们继续增加服务器数目后发现:Swordfish 每增加一台服务器提高 15tps,Camel 每增加一台服务器提高 10tps。在获得了这些数据后,我们才可以说,Swordfish 的水平可伸缩性比 Camel 好,尽管 Camel 在 5 个服务器以下会有更好的效率。
当构建企业应用系统时,关注硬件的可伸缩性往往比关注容量或效率更重要。如果需要,可伸缩性可以给予你获得更好性能的选择,可伸缩性也可以更容易实现。有时,设计人员费了九牛二虎之力才提高了少许容量,其开销还不如多买一些硬件。换句话说,假设 Camel 的费用比 Swordfish 高,高出的部分正好可以买几台服务器,那么选择 Swordfish 可能更合算,尽管你目前只需要 40tps。现在人们经常抱怨软件对硬件的依赖性越来越大,有时为了运行某些软件就不得不对硬件进行升级,就像我一样,为了用最新版本的 Word,就必须不断地升级笔记本电脑。但是总的来说,购买新硬件还是比修改旧软件来得便宜。同样,增加更多的服务器也比增加更多的程序员来得便宜——只要你的系统有足够的可伸缩性。
0.5 模式
模式的概念早就有了。我在这里不想把这段历史重新演绎一遍。只是想简单谈谈我对模式和它们为什么是描述设计的重要手段的一些看法。
模式没有统一的定义。可能最好的起点是 Christopher Alexander 给出的定义(这也是许多模式狂热者的灵感来源):“每一个模式描述了一个在我们周围不断重复发生的问题以及该问题解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动”[Alexander et al.]。尽管 Alexander 是建筑家,他谈论的是建筑模式,但其定义也能很好地适用于软件业。模式的核心就是特定的解决方案,它有效而且有足够的通用性,能解决重复出现的问题,模式的另一种视角是把它看成一组建议,而创造模式的艺术则是将很多建议分解开来,形成相互独立的组,在此基础上可以相对独立地讨论它们。
模式的关键点是它们源于实践。必须观察人们的工作过程,发现其中好的设计,并找出“这些解决方案的核心”。这并不是一个简单的过程,但是一旦发现了某个模式,他将是非常有价值的。对于我来说,价值之一是能够撰写这样一本参考书。你不必通读本书的全部内容,也不必通读所有有关于模式的书。你只需要了解到这些模式都是干什么的,它们解决什么问题,它们是如何解决问题的,就足够了。这样,一旦碰到类似问题,就可以从书中找出相应的模式。那时,再深入了解相应的模式也不迟。
一旦需要使用模式,就必须知道如何将它运用于当前的问题。使用模式的关键之一是不能盲目使用,这也是模式工具为什么都那么惨的原因。我认为模式是一种“半生不熟品”,为了用好它,还必须在自己的项目中把剩下的那一半“火候”补上。我本人每次在使用模式时,都会东改一点西改一点。因此你会多次看到同一解决方案,但没有一次是完全相同的。
每个模式相对独立,但又不彼此孤立。有时候它们相互影响,如影随形。例如,如果在设计中使用了领域模型,那么经常还会用到类表继承。模式的边界本来也是模糊的,我在本书中也尽量让它们各自独立。如果有人说“使用工作单元”,你就可以直接去看工作单元这个模式如何使用,而不必阅读全书。
如果你是一个有经验的企业应用设计师,也许会对大多数模式都很熟悉。希望本书不会给你带来太大的失望。(实际上我在前言里面已经提醒过了。)模式不是什么新鲜概念。因此,撰写模式书籍的作者们也不会声称我们“发明”了某某模式,而是说我们“发现”了某某模式。我们的职责是记录通用的解决方案,找出其核心,并把最终的模式记录下来。对于一个高级设计师,模式的价值并不在于它给予你一些新东西,而在于它能帮助你更好地交流。如果你和你的同事都明白什么是远程外观,你就可以这样非常简洁地交流大量信息:“这个类是一个远程外观模式。”也可以对新人说:“用数据传输对象模式来解决这个问题。”他们就可以查找本书来搞清楚如何做。模式为设计提供了一套词汇,这也是为什么模式的名字这么重要的原因。
本书的大多数模式是用来解决企业应用的,基本模式一章(见第 18 章)则更通用一些。我把它们包含进来的原因是:在前面的讨论中,我引用了这些通用的模式。
0.5.1 模式的结构
每个作者都必须选择表达模式的形式。一些人采用的表达基于模式的一些经典教材如 [Alexander et al.]、[Gang of Four] 或[POSA]。另一些人用他们自己的方式。我在这个问题上也斟酌了很久。一方面我不想象 GOF 一样太精炼,另一方面我还要引用他们的东西。这就形成了本书的模式结构。
第一部分是模式的名字。模式名非常重要,因为模式的目的之一就是为设计者们交流提供一组词汇。因此,如果我告诉你 Web 服务器是用前端控制器和转换试图构建的,而你又了解这些模式,那么你对我的 Web 服务器的架构就会非常清楚了。
接下来的两部分是相关的:意图和概要。意图用一两句话总结模式;概要是模式的一种可视化表示,通常是(但不总是)一个 UML 图。这主要是想给模式一个简单的概况,以帮助记忆。如果你对模式已经“心知肚明”,只是不知道它的名字,那么模式的意图和概要这两部分就能为你提供足够的信息。
接下来的部分描述了模式的动机。这可能不是该模式所能解决的唯一问题,但却是我认为最具代表性的问题。
“运行机制”部分描述了解决方案。在这一部分,我会讨论一些实现问题以及我遇到的变化情况。我会尽可能独立于平台来讨论——也有一个部分是针对平台来讨论的,如果不感兴趣可以跳过这部分。为了便于解释,我用了一些 UML 图来辅助说明。
“使用动机”部分描述了模式何时被使用。这部分讨论是使我选择该模式而不是其他模式的权衡考虑。本书中很多模式都可以相互替代,例如页面控制器和前端控制器可以相互替代。很少有什么模式是非它不可的。因此,每当我选择了一种模式之后,我总是问自己“你什么时候不用它?”这个问题也经常驱使我选择其他方案。
“进一步阅读”部分给出了与该模式相关的其他读物。它并不完善。我只选择我认为有助于理解模式的参考文献,所以我去掉了对本书内容没有价值的任何讨论,当然其中也可能会遗漏一些我不知道的模式。我也没有提到一些我认为可能读者无法找到的参考文献,再就是一些不太稳定的 Web 链接。
我喜欢为模式增加一个或几个例子。每个例子都非常简单,它们是用 Java 语言或 C# 语言编写的。我之所以选择两种语言,是因为它们可能是目前绝大多数专业程序员都能读懂的语言。必须注意,例子本身不是模式。当你使用模式时,不要想当然地认为它会和例子一样,也不要把例子看成某种形式的宏替换。我把例子编得尽量简单以突出其中模式相关的部分。当然,省略的部分并不是不重要,只是它们一般都特定于具体环境,这也是为什么模式在使用时一般都必须做适当调整的原因。
为了尽量使例子简单但是又能够突出核心意思,我主要选择那些简单而又明确的例子,而不是那些来自于系统中的复杂例子。当然,在简单和过分之间掌握平衡是不容易的,但是我们必须记住:过分强调具体应用环境反而会增加模式的复杂性,使得模式的核心内容不易理解。
这就是为什么我在选择例子时选取的是一些相互独立的例子而不是相互关联的例子的原因。独立的例子有助于对模式的理解。但是在如何将这些模式联合在一起使用上却支持不多。相互关联的例子则相反,它体现了模式间是如何相互作用的,但是对其中每个模式的理解却依赖于对其他所有模式的理解。理论上,是可以构造出既相互关联又相互独立的例子,但这是一项非常艰巨的工作——至少对于我来说是这样。因此,我选择了相互独立的例子。
例子中的代码本身也主要用来增强对思想的理解。因此,在其他一些方面考虑可能不够——特别是错误处理,在这方面,我没有花费很多笔墨,因为到目前为止,我还没有得出错误处理方面的模式。在此,那些代码纯粹用来说明模式,而并不是用来显示如何对任何特定的业务问题进行建模。
正是由于这些原因,我没有把这些代码放到我的网站上供大家下载。为了让那些基本的思想在应用设置下有所意义,本书的每个样例代码都充满着太多的“脚手架”来简化它们。
并不是每个模式中都包含上面所述的各个部分。如果我不能想出很好的例子或动机等内容,我就会把相应部分省略。
0.5.2 模式的局限性
正如我在前言中所述,对于企业应用开发而言,本书介绍的模式并不全面。我对本书的要求,不在于它是否全面,而在于它是否有用。模式这个领域太大了,单凭一个人的头脑是无法做到面面俱到的,更不用说是一本书了。
本书中所列的模式都是我在具体领域中遇到的,但这并不表明我已经理解了每一个模式以及它们之间的关系。本书的内容只是反映了我在写书时的理解,在编写本书的过程中,我对相关内容的理解也不断发展和加深,当然,在本书发表之后,我仍然希望本人对模式的理解还能够继续发展。对于软件开发而言,有一点是可以肯定的,那是软件开发永远不会停止。
当你使用模式时请记住:它们只是开始,而不是结束。任何作者去囊括项目开发中的所有变化和技术是不可能的。我编写本书的目的也只是作为一个开始,希望它能够把我自己的和我所了解的经验和教训传递给读者,你们可以在此基础上继续努力。请大家记住:所有模式都是不完备的,你们都有责任在自己的系统中完善它们,你们也会在这个过程中得到乐趣。
——选自:《企业应用架构模式》[Patterns of Enterprise Application Architecture] [英] 福勒 著;王怀民,周斌 译