第10章 · 原文阅读

设计模式

第10章 设计模式

面向对象技术为软件技术带来新的发展。人们运用面向对象的思想分析系统、为系统建模并设计系统,最后使用面向对象的程序语言来实现系统。但是面向对象的设计并不是一件很简单的事情,尤其是要设计出架构良好的软件系统更不容易。为了提高系统的复用性,需要进行一些“额外”的设计(这里的额外并不是无用的,而是指业务领域之外),定义类的接口、规划类的继承结构、建立类与类之间的关系。毋庸置疑,良好的设计可以让系统更容易地被复用、被移植和维护,而如何快速进行良好的设计则是设计模式要讨论的问题。设计模式是软件架构设计师的必修课,设计模式中蕴含的思想是架构设计师必须掌握的。

10.1 设计模式概述

在 20 世纪 70 年代,Christopher Alexander 提出了城市建筑的模式,他认为:模式就是描述一个不断发生的问题和该问题的解决方案。随后,Erich Gamma、Richard Helm、RalphJohnson 和 John Vlissides 写了一本著名的参考书《设计模式:可复用面向对象软件的基础》。

后人也因为这本书称这四个人为四人组,将这本书中描述的模式称为 GoF(Gang of Four)

设计模式。在这本书中,四人组将设计模式定义为:对被用来在特定场景下解决一般设计问题的类和互相通信的对象的描述。通俗地说,可以把设计模式理解为对某一类问题的通用解决方案。

10.1.1 设计模式的概念

首先,设计模式解决的是一类问题,例如工厂模式就是为了解决类创建的问题,而适配器模式则是为了解决类接口不匹配的问题。如果把解决 A 问题的设计模式使用在 B 问题上,结果肯定是张冠李戴了。所以在描述设计模式前,首先要描述这个设计模式究竟要解决什么样的问题。

其次,设计模式是一种通用的解决方案,而不是具体的,也不是唯一的。在 GoF 的书中对设计描述主要着重于思想的描述,虽然也给出了 C++的实现方法,但同样也可以使用Java 甚至非面向对象的语言实现。具体应用时可以根据实际情况进行相应的变化,例如,对于工厂模式就有很多种变化。

需要指出的是,虽然在 GoF 的著作中第一次提出了软件的设计模式,但设计模式并非这四人所创,它来源于很多项目中的成功设计,并将这些优雅的设计方式进行抽象、总结、归纳出来。

在学习设计模式时需要注意以下两点:

10.1.2 设计模式的组成

一般的,在描述一个设计模式时,至少需要包含四个方面:模式名称(Pattern name)、问题(Problem)、解决方案(Solution)、效果(Consequence)。这四个方面就是设计模式的四要素。名不正则言不顺,每种设计模式都有自己的名字,也就是模式名称;设计模式都有其应用的场合,即该设计模式意图解决的问题,超出了这个问题就不应该再应用这种模式,所以问题是设计模式的第二要素;设计模式的目的就是解决问题,所以在描述设计模式时当然要有解决问题的方法描述,这就是设计模式的另外一个要素——解决方案;虽然架构设计师知道应用设计模式可以提高架构质量,提高软件的复用性,但对于每一种设计模式而言,还有其更具体的效果描述,所以设计模式的最后一个要素就是效果。

这四个要素是描述设计模式时必不可少的部分。在本章中,除了描述模式的四要素外,还补充了该模式的 Java 实现和对该模式的引申,也就是说,本章中的模式将按照如下的方式描述:模式名称、意图解决的问题、模式描述、效果、实现、相关讨论。在 10.2 节中,将使用这种方式介绍几种有代表性的设计模式。

10.1.3 GoF 设计模式

GoF 的著作不反第一次总结了设计中的常用模式,还在学术上建立了软件设计模式的地位。因此,人们习惯上将 GoF 提出的 23 个模式统称为 GoF 模式,这 23 个模式分别简述如下。

在很多时候,使用 Decorator 模式可以不必继承出新的子类从而维护简洁的类继承结构。

在 9.2 节中将对 Decorator 模式作更详细的介绍。

10.1.4 其他设计模式

在 GoF 之后,人们继续对设计模式进行发掘,总结出更多的设计模式。在 J2EE 应用领域,人们也对使用 J2EE 框架开发的应用程序总结出一系列设计模式,本节对几种较有代表性的 J2EE 设计模式进行简要介绍。因为这些设计模式是同 J2EE 技术紧密相关的,所以本节中将会使用一些 J2EE 技术术语。

10.1.5 设计模式与软件架构

软件架构描述了软件的组成,例如,经典的“4+1”视图,将软件架构通过逻辑视图、开发视图、进程视图、物理视图及场景视图来进行描述。在这些视图中,描述了软件系统中类之间的关系、进程之间的关系、软件和硬件的结合等问题。一般来说,软件架构更倾向于从整体和全局上描述软件的组成。

而设计模式则更侧重于类与类、对象与对象之间的关系。例如在逻辑视图中,可以使用多种设计模式来组织类与类之间的关系。因此,有很多人认为,设计模式和软件架构是面向不同层次问题的解决方案。

同设计模式一样,软件架构也有一些固定的模式,通常称为架构风格。常见的架构风格有分层架构、客户端—服务器架构、消息总线、面向服务的架构(Service-Oriented Architecture,SOA)等。

软件架构风格同设计模式在某种含义上是一致的。设计模式和软件架构中蕴含的很多思想是一致的。无论是架构风格还是设计模式,人们在追求良好设计的过程中,将一些常见解决方案总结、整理出来,形成固定的风格与模式。例如消息总线的架构风格同 Observer 模式就有神似之处。因此,掌握设计模式对于软件架构设计有非常大的帮助。

10.1.6 设计模式分类

可以说,设计模式是面向问题的,即每一种设计模式都是为了解决一种特定类型的问题。

因此,根据设计模式要解决的问题将设计模式分为三类,分别为创建型、结构型和行为型。

事实上,面向对象的设计中,需要解决的就是:如何管理系统中的对象、如何组织系统中的类与对象、系统中的类与对象如何相互通信。这三类设计模式分别解决了这三个方面的问题。

创建型设计模式主要解决对象创建的问题。在最简单的情况下,在程序中定义类,在使用时创建一个对象实例。但在实际开发中,对象的创建会变得复杂很多,这时就需要使用创建型设计模式解决创建对象的问题。

随着开发系统的不断扩张,系统功能更加丰富,模块之间的复用越来越多,系统中类与对象的结构变得更加复杂。如果缺乏良好的设计,这些类之间的关系将会变得非常混乱。结构型设计模式就是为了解决这些问题的。

除了这种分类方法外,GoF 还提出了可以根据设计模式主要应用于类还是对象来对设计模式进行分类,对于这种分类方法就不再赘述了。综合这两种分类方法,可以把 GoF 模式进行分类,如表 10-1 所示。

随着 GoF 设计模式的提出,后人也总结出了更多的良好设计的范本,并根据其他的方法进行分类。例如,在《Core J2EE Patterns》一书中,作者将书中列举的 Design Pattern 分为表现层模式、业务层模式和综合层模式。根据这种分类方法,可以得到应用于 J2EE 框架的设计模式图谱,如表 10-2 所示。

10.2 设计模式及实现

本节就几种常用的设计模式,讨论其实现问题。

10.2.1 Abstract Factory 模式

1. 模式名称

Abstract Factory,也经常称之为抽象工厂模式。

2.意图解决的问题

在程序中创建一个对象似乎是不能再简单的事情,其实不然。在大型系统开发中存在以下问题:

抽象工厂模式就可以解决这样的问题,根据不同的配置或上下文环境加载具有相同接口的不同类实例。

3. 模式描述

Abstract Factory 模式的结构如图 10-1 所示。

就如同抽象工厂的名字一样,Abstract Factory 类将接受 Client 的“订单”——Client 发送过来的消息,使用不同的“车间”——不同的 Concrete Factory,根据已有的“产品模型”

——Abstract Product,生产出特定的“产品”——Product。不同的车间生产出不同的产品供客户使用,车间与产品的关系是一一对应的。由于所有的产品都遵循产品模型——AbstractProduct,具有相同的接口,所以这些产品都可以直接交付客户使用。

在抽象工厂模式中,Abstract Factory 可以有多个类似于 Create Product()的虚方法,就如同一个工厂中有多条产品线一样。Create Product1()创建产品线 1,Create Product2()创建产品线 2。

希赛教育专家提示:在 Abstract Factory 中,Create Product()方法与 Abstract Product 是一一对应的,而 Concrete Factory 的数量同实际的 Product 的数量是一致的。

4. 效果

应用 Abstract Factory 模式可以实现对象可配置的、动态的创建。灵活运用 AbstractFactory 模式可以提高软件产品的移植性,尤其是当软件产品运行于多个平台,或有不同的功能配置版本时,抽象工厂模式可以减轻移植和发布时的压力,提高软件的复用性。

5. 相关讨论

在实际应用中,Abstract Factory 可以有更灵活的变化。事实上,如果仔细观察AbstractFactory 模式就可以发现,对于 Client 来说,最关注的就是在不同条件下获得接口一致但实现不同的对象,只要避免类名的硬编码,采用其他方式也可以实现。所以也可以采用其他的方式实现。例如在 Java 中就可以采用接口的方式实现,如图 10-2 所示。

图 10-2 中所描绘的类就应用了 Abstract Factory 的思想,采用面向接口的方式,简单实现了工厂模式。Product Factory 既可以看作抽象工厂 Abstract Factory,也可以看作具体的工厂(车间) Concrete Factory 。其中提供了一个获得产品的方法: getProduct(productName:String),该方法将根据产品的名称创建特定的产品,并返回。所有的产品都实现了 Product 接口,可以在客户程序中加以使用。同抽象工厂模式一样,工厂中获得对象的方法(getProduct())与实际的产品线数量是一致的,如果要增加新的产品线——例如定义新的产品接口 ProductX,需要增加相应的 getProduct()方法。由于这种方式把 AbstractFactory 和 Concrete Factory 合并为一个 Product Factory,所以增加新的实现 Product 接口的 Productn 不需要修改任何代码。

除了这种面向接口的方法外,工厂模式还可以有更多的变化。前面已经说过,学习设计模式最终要的是学习设计思想,学习了工厂模式后,应该知道:

10.2.2 Singleton 模式

1. 模式名称

Singleton,也常称之为单件模式或单根模式。

2. 意图解决的问题

在软件开发中,开发人员希望一些服务类有且仅有一个实例供其他程序使用。例如,短消息服务程序或打印机服务程序,甚至对于系统配置环境的控制,为了避免并发访问造成的不一致,也希望仅为其他程序提供一个实例。

对于供整个系统使用的对象可以使用一个全局变量,不过全局变量仅能保证正确编码时使用了唯一的实例。但随着系统不断的扩张,开发队伍的扩大,仍然无法保证这个类在系统中有且仅有一个实例。

3. 模式描述

Singleton 模式可以解决上面的问题,其结构如图 10-3 所示。

从结构角度而言,Singleton 是最简单的一个模式,不过其用途很广。在 Singleton 中,通过将 Singleton 类的构造函数设为 protected 型(或 private)来防止外部对其直接初始化。需要访问 Singleton 的程序必须通过 getInstance()方法来获得一个 Singleton。在getInstance() 中仅创建一次 uniqueInstance 就可以保证系统中的唯一实例。

对于 Singleton 中的 uniqueInstance 有两种不同的初始化策略(Lazy Initialization 和Early Initialization),在实现中将分别给出这两种初始化策略的代码。

4. 效果

使用 Singleton 模式可以保证系统中有且仅有一个实例,这对于很多服务类或者环境配置类来说非常重要。

5. 相关讨论

在使用 Singleton 模式时,需要注意:

但在 Java、C#等语言中,则只有采用其他的方法实现。不过 Singleton 不等同于 static 类,所以一般来说,系统中不会也不应该出现很多 Singleton 类,在不支持 Template 的语言中逐一实现也未尝不可。

如果一定要在不支持 Template 的语言中实现批量的 Singleton 类,可以参考有关文献中的方法。

10.2.3 Decorator 模式

1. 模式名称

Decorator 模式,又称装饰模式或油漆工模式。

2. 意图解决的问题

在开发时,经常会发现为类预先设计的功能并不够强大,需要增强或扩展类的功能。解决这个问题的最简单的办法是继承出一个新的类,并扩展相应的方法。但是这样做会产生大量的子类,让系统中类的层次结构变得复杂且混乱。Decorator 模式通过在原有类的基础上包装一层来解决功能扩展的问题。

3. 模式描述

Decorator 模式的结构如图 10-4 所示。

DecoratorComponent 是 ConcreteComponent 的装饰类,它们继承自同一个抽象类Component,拥有相同的接口。对于 ConcreteComponent 的装饰可能不止一种,因此又继承出 ConcreteDecorator1 和 ConcreteDecorator2 来作为具体的装饰类。这个结构在类层次上是很清晰的,比静态继承更具有灵活性。图 10-5 中使用顺序图描述了使用 Decorator 类的方法。

通过图 10-4 的方法,对于接口相同的 operation(),在运行期可以根据客户的选择具有不同的行为特征,实现功能动态的扩展。

4. 效果

Decorator 模式可以动态地扩展类的功能,同时又避免继承出大量的子类,比继承的方法更灵活。由于 Decorator 提供了动态的扩展方法,因此可以随时根据具体需求产生新的装饰类,从而可以在一定程度上避免层次较高类的复杂性。但是大量使用 Decorator 模式会造成系统中出现很多接口类似的小装饰对象,这样就造成系统可维护性的下降。

5. 相关讨论

Decorator 模式提供了一种动态为类扩展功能的方法,有着较广的应用。例如,在开源项目 displaytag(一个提供 JSP 标签扩展的开源项目,可以简单地在网页中画出美观的表格,http://displaytag.sourceforge.net/)中就提供了以 Decorator 方式扩展功能,进行二次开发的接口,在 JDK 的 I/O API 中,也大量使用了 Decorator 模式。

同其他模式一样,滥用 Decorator 会降低系统的可维护性。如果开发出来的装饰类仅被单一的地方使用或只进行了相当简单的处理,就需要考虑是否有必要使用 Decorator 模式了

10.2.4 Facade/Session Facade 模式

1. 模式名称

Facade 模式,又称外观模式,也有人很形象地把它翻译成“门面模式”。

2. 意图解决的问题

在程序中,经常会用到其他若干个子系统。在不作任何处理的时候,需要了解每一个子系统的接口,并使用这些接口进行调用,于是系统就如图 10-6 所示一样混乱。

这些调用不但让结构变得混乱,客户程序和各子系统的耦合性也大大增加,扩展与维护都变得相当困难。

3. 模式描述

Facade 模式的结构如图 10-7 所示。

如图 10-7 所示,Facade 模式通过在原有系统前增加一层的方法,封装这些子系统的接口,对外提供一致的访问接口,解决了上面的问题。

4. 效果

Facade 模式屏蔽了子系统的细节,降低了客户程序使用这些子系统的复杂度,同时也降低了客户程序和子系统的耦合程度。这样就从需要让所有的人了解所有的子系统接口变成让个别专家抽象子系统的接口。

Facade 模式应用起来非常灵活,也没有特定的实现,其应用的关键就是抽象出合理的Facade 接口。

5. 相关讨论

Facade 模式很好地体现了封装的思想,它封装的是一个子系统的内部结构。例如,经常对数据访问对象(Data Access Object,DAO)进行封装,使数据访问同具体的数据库相分离。客户程序只要知道最外层的数据库访问构件的接口就可以操纵任何支持的数据库。

在 EJB 开发领域,又根据 Facade 模式引申出 Session Facade 模式。

EJB 是一种分布式构件,J2EE 将对 EJB 的访问定义为远程访问。例如,使用 Servlet 访问 EJB 时,即使 Servlet 和 EJB 部置于同一台主机,但由于 Servlet 和 EJB 分置于不同的容器(Servlet 在 Web 容器中,EJB 在 EJB 容器中),Servlet 的默认访问方式也是通过本机的回播地址(127.0.0.1)访问 EJB,从而造成 EJB 访问的效率问题。而 EJB 中封装了复杂的业务对象,把这些业务对象的全部暴露给外部也不利于系统的维护。所以通常使用Session Bean 封装这些业务逻辑,提高访问效率,减少内部业务逻辑和外部访问者的耦合,封装服务器中的对象模型,如图 10-8 所示。

在 Session Facade 模式中,一般使用 Session Bean 作为业务逻辑的接口,在 SessionBean 中进行事务处理,确保客户对 SessionBean 的每一次访问即完成一次业务操作。使用Session Facade 模式除了获得 Facade 模式的优点外,还可以减少远程调用 EJB 的次数,降低网络接口的负载,提高性能。

10.2.5 Mediator 模式

1. 模式名称

Mediator 模式,又称中介者模式。

2. 意图解决的问题

在一个复杂系统中会有很多对象,这些对象之间会相互通信,从而造成对象的相互依赖。

修改其中一个对象可能会影响到其他若干对象,系统中对象的复杂耦合关系造成系统可维护性的降低,系统显得混乱且难以理解。Mediator 通过封装一组对象间的通信为系统解耦。

3. 模式描述

Mediator 模式的结构如图 10-9 所示。

在 Mediator 模式中,一组对象 ——抽象类 Colleague 的子类,通过中介类 ——ConcreteMediator 进行通信,中介类 ConcreteMediator 继承了抽象类 Mediator,其中需要实现 Mediator 中定义的通信接口,保证中介类和相互通信类之间接口的一致。

Mediator 模式看上去类似于消息机制,其实和消息机制有很大的区别。Mediaor 不需要负责消息的排队、优先级等处理,但它必须根据 Colleague 发送的消息作出响应,并向特定的 Colleague 类发送消息。换句话说,ConcreteMediator 类中封装了 Colleague 之间的通信,就好比人的大脑,接收到眼睛发送的“前面有一个坑”的消息,就会向脚发送“停止前进”的消息。

4. 效果

Mediator 封装了一组对象间的通信,降低了 Colleague 之间的耦合性,单个 Colleague的变化不会影响到其他的对象。还举前面大脑的例子,在行走的时候,大脑接收到眼睛发送的“前面有状况”的消息会通知脚“停止前进”,而在开车的时候,大脑接收这条消息后则会向脚发送“踩下刹车”的消息。

不过当 Colleague 比较多,且其中的关系复杂时,中介类 ConcreteMediator 会变得非常复杂,难以维护。

5. 相关讨论

Mediator 模式在 GUI 方面有较多的使用。当一个窗体或表单或对话框中有多个需要互相通信的对象时——例如显示部门中人员的下拉列表需要根据部门下拉列表不同的选择而变化,使用 Mediator 封装通信可以降低这些对象的耦合性,提高系统的可维护性。

希赛教育专家提示:Mediator 经常会变得非常复杂,不但每一个需要通信的对象都需要知道 Mediator 的存在,而且 Mediator 也需要知道每一个通信的对象的实现。所以,一般仅在较小的范围内使用 Mediator 模式,否则过多的 Colleague 类造成中介类的难以维护会抵消掉该模式带来的优点,反而让系统更难以维护。

10.2.6 Observer 模式

1. 模式名称

Observer 模式,又称观察者模式。

2. 意图解决的问题

由于对象封装的特性,一般的,在系统中对象状态的变化都是独立的,即 A 对象状态的变化不会立即反映到 B 对象。但是,在系统中很多对象是相互关联的,例如对于一个股票行情系统,股票状态的变化需要同时反映到多个视图中——实时行情、技术分析图表等。

对于这个问题,最简单的解决办法就是硬编码这些关联关系。但是这样会造成系统中对象的紧密耦合,系统难以维护和复用。

3. 模式描述

Observer 模式的结构如图 10-10 所示。

抽象类 Subject 定义了主题类——也就是被观察者的接口,它的子类 ConcreteSubject的任何状态改变将会通知全部的观察者——ConcreteObserver。为了保持接口的一致性,这些观察者继承自相同的抽象类 Observer。

抽象类在 Subject 中保存有观察者的列表——observers,并通过 attach 和 detach 方法来动态地添加或移出一个特定的观察者。这种采用订阅方法来添加的观察者并不知道还有其他的观察者,它仅仅能够接收被观察的主题对象的状态改变。在 notify 方法中,主题类将根据目前加入订阅的观察者列表 observers 来向每一个观察者发出状态改变的消息。

ConcreteSubject 中的 setState()方法表明对象的状态发生了变化,该方法会调用 notify方法对所有的观察者进行更新,如图 10-11 所示。

4. 效果

在 Observer 模式中,实现了具体对象和观察者之间的抽象耦合。虽然 Subject 了解目前有哪些观察者需要捕获自己状态的变化,但它并不了解这些观察者要做什么;而每一个观察者仅仅知道自己捕获到对象的变化,但并不清楚目前有多少观察者在观察对象的状态,也不需要通知其他的观察者。这样,动态的增加和移除观察者是非常简单的。

通过 Observer 模式可以实现对象间的消息广播,这在很多处理中非常有用。不过广播的副作用是增加了系统的负载,任何消息都将被自动地发送到每一个观察者中,每一个观察者都将根据这些消息作出响应,这些响应未必都是必要的。

5. 相关讨论

Observer 模式将一对多的对象依赖转化为抽象耦合,提高系统的复用价值,得到了广泛的应用。Smalltalk 的 MVC 架构中就应用了 Observer 模式,将 Model 的变化传递到View 中。

但是 Observer 使用不当会造成很多问题。在图 9-9 中描述的更新方法是在 setState()后由 Subject 自动执行 nofity(),向所有的 Observer 发送通知,这种方法可以保证所有的更新都能够通知到观察者。但是对象状态并不总是孤立的,客户程序很可能在执行了setState1()方法之后紧接着执行 setState2()方法,甚至要连续对对象的状态作若干次的更改。

这样,每一次更改都会带来一个 noifty()事件,这些更改被放大到每一个 Observer 对象中。

并发执行观察者的 update()方法很可能造成程序的错误,而独占的访问 update()方法又会造成程序阻塞。

因此有时会采用如图 10-12 所示的方法发送对象状态变化的通知。

这种方法在客户程序执行了一系列改变对象状态的方法后调用 notify()方法,避免了上面的问题,不过这种方法首先不能保证对象状态的变化一定会被通知到各个观察者,其次还造成观察者对客户程序的不透明。

所以,在使用 Observer 模式时一定要注意精确地定义对象状态变化的依赖关系,以避免这些问题。

前面也提到使用 Observer 可以实现消息的广播。广播是一柄双刃剑,虽然可以简化一对多的消息发送但也将带来一系列的问题。了解计算机网络的读者知道以太网中的广播风暴问题,在 Observer 模式中需要尽力避免广播的扩大,在 update()对象中避免更新 Subject类的状态,尤其不能更新自己观察的 Subject 类的状态。否则,可能会造成对象间的消息无穷循环下去。

10.2.7 Intercepting Filter 模式

1. 模式名称

Intercepting Filter 模式,又称筛选器模式。

2. 意图解决的问题

在使用 MVC 架构进行 Web 应用开发时,通常需要对来自于客户的请求进行一些预处理,如验证客户身份、验证请求来源、对请求解码等,然后再传递给控制器。如果把这些预处理都交由控制器来完成,将增加控制器的复杂度,而且难以维护和扩展。

3. 模式描述

Intercepting Filter 模式的结构如图 10-13 所示。

FilterManager 负责调度整个 FilterChain,它将在请求到达 Target 前拦截请求,并传递给 FilterChain,由 FilterChain 中的过滤器依次进行预处理。直到请求经过最后一个过滤器后才完成了全部的预处理,然后由 FilterManger 把请求转发给实际的目标。使用InterceptingFilter 模式时的时序关系如图 10-14 所示。

4.效果

使用 Intercepting Filter 模式使得预处理的逻辑和真正的处理逻辑分离,进行实际处理的 Target 只需要关心具体的逻辑,而同请求相关的预处理都放在 FilterManager 中进行。

同时解除了这两类处理的耦合性,扩展、修改预处理过程变得容易,系统具有更好的维护性和扩展性。

5. 相关讨论

虽然 Intercepting Filter 作为一种 J2EE 模式出现在有关文献中,但实际上有很广泛的应用。不仅应用 J2EE 的开发可以使用该模式分离预处理过程,使用.Net 框架等其他的 Web应用时也可以使用 Intercepting Filter。

仔细观察 Intercepting Filter 模式就可以发现,它与 Decorator 模式有些类似,都是在真正的处理对象前增加一层扩展对象的操作。只不过实现起来有很大的差别。

希赛教育专家提示:完全可以让 Intercepting Filter 变得更通用,更容易配置。如果对于不同的请求需要使用不同的 FilterChain——例如根据客户不同的编码方式使用不同的解码程序——可以使用工厂模式来动态创建 Filter 对象进行处理。类似的灵活使用设计模式的方法还有很多,也可以用于各种模式,需要架构设计师根据实际情况进行深入的思考。

10.3 设计模式总结

10.2 节详细讨论了 7 种设计模式:Abstract Factory、Singleton、Decorator、Facade/SessionFacade、Mediator、Observer 和 Intercepting Filter。目前总结出的设计模式远远不止这些,除了 23 种 GoF 模式外,很多学者都在这方面进行了有益的尝试,总结出了大量良好设计的范例。

学习设计模式最重要的是理解,而不是生搬硬套。每种设计模式中都包含着良好的设计架构的思想,如隐藏内部细节、降低耦合度等,越是复杂的系统越需要这些思想的支撑。观察人类社会就可以发现很多与设计模式相同的地方。每一个人都是一个封装良好的对象,通过感官和行动提供了与外界沟通的媒介。人类对外接收消息的方式不过是视觉、听觉、嗅觉、触觉等有限的几种,但这些感觉器官封装了内部复杂的结构;可以使用望远镜、电话或其他的工具来扩展能力;对于人类的复杂组织,有间接沟通的渠道,中介所提供了交流的平台;

新闻机构则可以让人们随时了解其他人和事的最新状态……类似的例子还可以举出来很多,如经纪人与 Proxy 模式非常相近,而绝大多数的组织看起来都像 Composite。软件系统的最终目的是辅助人类的活动,以良好的方式抽象现实世界才可以获得良好的设计,单纯的死记硬背这些模式不会有任何价值,与实际情况相联系才能得出易维、护易复用的系统来。

在使用中,经常会把几种模式综合起来解决复杂的问题,例如 10.2.7 中提到的Intercepting Filter 和工厂模式的结合。这需要系统架构设计师具体问题具体分析,综合利用设计模式消除系统中的混乱与耦合,提高系统复用性。

希赛教育专家提示:要切记不能滥用设计模式,尤其在一些简单系统中。如果系统中的对象都用工厂模式创建、系统中的工具类都设计成 Singleton、两个对象间的通信还要硬加上一层 Mediator 等都是不可取的,只能毫无价值地提高系统复杂度,反而不利于系统的理解与维护。除最初的设计外,重构也是一个很好的时机,系统架构设计师可以在重构的时候根据需要逐步应用设计模式改良系统,提高系统的维护性和复用性。