03-设计模式概述
这篇文章介绍了设计模式的定义、基本要素(模式名称、问题、解决方案、效果)及其分类(按目的分为创建型、结构型、行为型;按范围分为类模式和对象模式)。还列举了GoF的23种经典设计模式及J2EE模式,并总结了设计模式的优点,如提高代码复用性、可维护性、开发效率,促进团队沟通等。设计模式是经过验证的、可复用的代码设计经验,能帮助开发者构建高质量软件系统。
1、设计模式的定义与分类
设计模式的出现可以让我们站在前人的肩膀上,通过一些成熟的设计方案来指导新项目的开发和设计,更加方便地复用成功的设计和体系结构。
1.1、设计模式的定义
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解、提高代码的可靠性。
1.2、设计模式的基本要素
设计模式一般有如下几个基本要素:模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式,其中的关键元素包括以下四个方面。
1、模式名称
模式名称(Pattern name)通过一两个词来描述模式的问题、解决方案和效果,以便更好地理解模式并方便开发人员之间的交流,绝大多数模式都是根据其功能或模式结构来命名的。在学习设计模式时,首先应该准确记忆该模式的中英文模式名,在已有的类库中,很多使用了设计模式的类名通常包含了所使用的设计模式的模式名称,如果一个类类名为 XXXAdapter,则该类是一个适配器类,在设计时使用了适配器模式,如果一个类类名为 XXXFactory,则该类是一个工厂类,它一定包含了一个工厂方法用于返回一个类的实例对象。
2、问题
问题(Problem)描述了应该在何时使用模式,它包含了设计中存在的问题以及问题存在的原因。这些问题有些是一些特定的设计问题,如怎样使用对象封装状态或者使用对象表示算法等,也可能是系统中存在不灵活的类或对象结构,导致系统可维护性较差。有时候,在模式的问题描述部分可能会包含使用该模式时必须满足的一系列先决条件。如在使用桥接模式时系统中的类必须存在两个独立变化的维度,在使用组合模式时系统中必须存在整体和部分的层次结构等。在对问题进行描述的同时实际上就确定了模式所对应的使用环境以及模式的使用动机。
3、解决方案
解决方案(Solution)描述了设计模式的组成成分,以及这些组成成分之间的相互关系,各自的职责和协作方式。模式是一个通用的模板,它们可以应用于各种不同的场合,解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。在学习设计模式时,解决方案通过类图和核心代码来加以说明,对于每一个设计模式,必须掌握其类图,理解类图中每一个角色的意义以及它们之间的关系,同时需要掌握实现该模式的一些核心代码,以便于在实际开发中合理应用设计模式。
4、效果
效果(Consequences)描述了模式应用的效果以及在使用模式时应权衡的问题。效果主要包含模式的优缺点分析,我们应该知道,没有一个解决方案是百分之百完美的,在使用设计模式时需要进行合理的评价和选择。一个模式在某些方面具有优点的同时可能在一方面存在缺陷,因此需要综合考虑模式的效果。在评价效果时,我们通过结合上一章所学的面向对象设计原则来进行分析,如判断一个模式是否符合单一职责原则,是否符合开闭原则等。
除了上述的四个基本要素,完整的设计模式描述中通常还包含该模式的别名(其他名称)、模式的分类(模式所属类别)、模式的适用性(在什么情况下可以使用该设计模式)、模式角色(即模式参与者,模式中的类和对象以及它们之间的职责)、模式实例(通过实例来进一步加深对模式的理解)、模式应用(在已有系统中该模式的使用)、模式扩展(该模式的一些改进、与之相关的其他模式及其他扩展知识)等。
本书将按照以下次序来介绍设计模式:
- 模式动机与定义:通过一些简单问题引出模式,了解该模式可以解决的问题,并对模式进行准确的定义(包括中文定义和英文定义)。
- 模式结构与分析:模式结构图(类图)及角色分析,理解该模式解决方案的构成以及成分的关系,并结合示例代码或实例对模式结构和角色进行进一步说明。
- 模式实例与解析:通过一或两个实例对模式进行深入学习,了解如何在实际开发中应用该模式。在本书中,大部分模式都提供了两个实例,一个来源于现实生活,方便对模式的理解;另一个来源于软件开发。
- 模式效果与应用:对每一个模式的优缺点进行分析,学会识别模式的适用场景,了解在已有系统中模式的使用情况。
- 模式扩展:模式的一些改进方案,包括模式功能的增强和简化,与其他模式的联用以及模式的变异,还包括与该模式相关的其他扩展知识。
1.3、设计模式的分类
设计模式一般有以下两种分类方式。
- 根据其目的(模式是用来做什么的)可分为创建型(Creational)、结构型(Structural)和行为型(Behavioral)三种:
- 创建型模式主要用于创建对象,GoF提供了5种创建型模式,分别是工厂方法模式(Factory Method)、抽象工厂模式(Abstract Factory)、建造者模式(Builder)、原型模式(Prototype)和单例模式(Singleton);
- 结构型模式主要用于处理类或对象的组合,GoF提供了7种结构型模式,分别是适配器模式(Adapter)、桥接模式(Bridge)、组合模式(Composite)、装饰模式(Decorator)、外观模式(Facade)、享元模式(Flyweight).和代理模式(Proxy);
- 行为型模式主要用于描述对类或对象怎样交互和怎样分配职责,GoF提供了11种行为型模式,分别是职责链模式(Chain of Responsibility)、命令模式(Command)、解释器模式(Interpreter)、迭代器模式(Iterator)、中介者模式(Mediator)、备忘录模式(Memento)、观察者模式(Observer)、状态模(State)、策略模式(Strategy)、模板方法模式(TemplateMethod)和访问者模式(Visitor)。
- J2EE 模式这些设计模式特别关注表示层。这些模式是由 Sun Java Center 鉴定的。
- 根据范围,即模式主要是用于处理类之间关系还是处理对象之间的关系,可分为类模式和对象模式两种:
- 类模式处理类和子类之间的关系,这些关系通过继承建立,在编译时刻就被确定下来,是属于静态的。
- 对象模式处理对象间的关系,这些关系在运行时刻变化,更具动态性。根据“合成复用原则”,在系统设计时,我们应该尽量用关联关系来取代继承关系,因此大部分模式都属于对象模式,纯的类模式很少。
2、GoF设计模式简介
在GoF的经典著作《设计模式:可复用面向对象软件的基础》一书中一共描述了23种设计模式,这23种模式分别如表所示。
| 范围\目的 | 创建型模式 | 结构模式 | 行为模式 | J2EE 模式 |
|---|---|---|---|---|
| 类模式 | 工厂方法模式 | (类)适配器模式 | 解释器模式 模板方法模式 | 业务模式 |
| 对象模式 | 抽象工厂模式 建造者模式 原型模式 单例模式 | (对象)适配器模式 桥接模式 组合模式 装饰器模式 外观模式 享元模式 代理模式 | 职责链模式 命令模式 迭代器模式 中介模式 备忘录模式 观察者模式 状态模式 策略模式 访问者模式 | MVC 模式 业务代表模式 组合实体模式 数据访问对象模式 前端控制器模式 拦截过滤器模式 服务定位器模式 传输对象模式 |
下面简单对GoF23种设计模式进行说明,如表所示。
| 模式类别 | 模式名称 | 模式说明 |
|---|---|---|
| 创建型模式 (Creational Patterns) | 抽象工厂模式 (Abstract Factory) | 提供了一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类 |
| 建造者模式 (Builder) | 将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示 | |
| 工厂方法模式 (Factory Method) | 将类的实例化操作延迟到子类中完成,即由子类来决定究竟应该实例化(创建)哪一个类 | |
| 原型模式 (Prototype) | 通过给出一个原型对象来指明所要创建的对象的类型,然后通过复制这个原型对象的办法建出更多同类型的对象 | |
| 单例模式 (Singleton) | 确保在系统中某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 | |
| 结构型模式 (Structural Patterns) | 适配器模式 (Adapter) | 将一个接口转换成客户希望的另一个接口,从而使接口不兼容的那些类可以一起工作 |
| 桥接模式 (Bridge) | 将抽象部分与它的实现部分分离,使它们都可以独立地变化 | |
| 组合模式 (Composite) | 通过组合多个对象形成树形结构以表示“整体-部分”的结构层次,对单个对象(即叶子象)和组合对象(即容器对象)的使用具有一致性 | |
| 装饰模式 (Decorator) | 动态地给一个对象增加一些额外的职责 | |
| 外观模式 (Facade) | 为复杂子系统提供一个统一的入口 | |
| 享元模式 (Flyweight) | 通过运用共享技术有效地支持大量细粒度对象的复用 | |
| 代理模式 (Proxy) | 给某一个对象提供一个代理,并由代理对象控制对原对象的引用 | |
| 行为型模式 (Behavioral Patterns) | 职责链模式 (Chain of Responsibility) | 避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止 |
| 命令模式 (Command) | 将一个请求封装为一个对象,从而使得请求调用者和请求接收者解耦 | |
| 解释器模式 (Interpreter) | 描述如何为语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子 | |
| 迭代器模式 (Iterator) | 提供了一种方法来访问聚合对象,而不用暴露这个对象的内部表示 | |
| 中介者模式 (Mediator) | 通过一个中介对象来封装一系列的对象交互,使得各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互 | |
| 备忘录模式 (Memento) | 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态 | |
| 观察者模式 (Observer) | 定义了对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新 | |
| 状态模式 (State) | 允许一个对象在其内部状态改变时改变它的行为 | |
| 策略模式 (Strategy) | 定义一系列算法,并将每一个算法封装在一个类中,并让它们可以相互替换,策略模式让算法独立于使用它的客户而变化 | |
| 模板方法模式 (Template Method) | 定义一个操作中算法的骨架,而将一些步骤延迟到子类中 | |
| 访问者模式 (Visitor) | 表示一个作用于某对象结构中的各元素的操作,它使得用户可以在不改变各元素的类的前提下定义作用于这些元素的新操作 | |
| J2EE 模式 (J2EE Patterns) | MVC 模式 (MVC Pattern) | MVC 模式代表 Model-View-Controller(模型-视图-控制器) 模式。这种模式用于应用程序的分层开发。 |
| 业务代表模式 (Business Delegate Pattern) | 用于对表示层和业务层解耦。它基本上是用来减少通信或对表示层代码中的业务层代码的远程查询功能。在业务层中我们有以下实体。 | |
| 组合实体模式 (Composite Entity Pattern) | 用在 EJB 持久化机制中。一个组合实体是一个 EJB 实体 bean,代表了对象的图解。当更新一个组合实体时,内部依赖对象 beans 会自动更新,因为它们是由 EJB 实体 bean 管理的。以下是组合实体 bean 的参与者。 | |
| 数据访问对象模式 (Data Access Object Pattern) | 用于把低级的数据访问 API 或操作从高级的业务服务中分离出来。以下是数据访问对象模式的参与者。 | |
| 前端控制器模式 (Front Controller Pattern) | 用来提供一个集中的请求处理机制,所有的请求都将由一个单一的处理程序处理。该处理程序可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。 | |
| 拦截过滤器模式 (Intercepting Filter Pattern) | 用于对应用程序的请求或响应做一些预处理/后处理。定义过滤器,并在把请求传给实际目标应用程序之前应用在请求上。过滤器可以做认证/授权/记录日志,或者跟踪请求,然后把请求传给相应的处理程序。以下是这种设计模式的实体。 | |
| 服务定位器模式 (Service Locator Pattern) | 用在我们想使用 JNDI 查询定位各种服务的时候。考虑到为某个服务查找 JNDI 的代价很高,服务定位器模式充分利用了缓存技术。在首次请求某个服务时,服务定位器在 JNDI 中查找服务,并缓存该服务对象。当再次请求相同的服务时,服务定位器会在它的缓存中查找,这样可以在很大程度上提高应用程序的性能。以下是这种设计模式的实体。 | |
| 传输对象模式 (Transfer Object Pattern) | 用于从客户端向服务器一次性传递带有多个属性的数据。传输对象也被称为数值对象。传输对象是一个具有 getter/setter 方法的简单的 POJO 类,它是可序列化的,所以它可以通过网络传输。它没有任何的行为。服务器端的业务类通常从数据库读取数据,然后填充 POJO,并把它发送到客户端或按值传递它。对于客户端,传输对象是只读的。客户端可以创建自己的传输对象,并把它传递给服务器,以便一次性更新数据库中的数值。以下是这种设计模式的实体。 |
需要注意的是,这23种设计模式并不是孤立存在的,很多模式彼此之间存在联系,如在访问者模式中操作对象结构中的元素时通常需要使用迭代器模式,在解释器模式中定义终结符表达式和非终结符表达式时可以使用组合模式;此外,还可以通过组合两个或者多个模式来设计同一个系统,在充分发挥每一个模式优势的同时使它们可以协同工作,完成一些更复杂的设计工作。
3、设计模式的优点
设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案将避免我们做一些重复性的工作,而且可以设计出高质量的软件系统。具体来说,设计模式的主要优点如下:
- 设计模式融合了众多专家的经验,并以一种标准的形式供广大开发人员所用,它提供了一套通用的设计词汇和一种通用的语言以方便开发人员之间沟通和交流,使得设计方案更加通俗易懂。对于使用不同编程语言的开发和设计人员可以通过设计模式来交流系统设计方案,每一个模式都对应一个标准的解决方案,设计模式可以降低开发人员理解系统的复杂度。
- 设计模式使人们可以更加简单方便地复用成功的设计和体系结构,将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。设计模式使得重用成功的设计更加容易,并避免那些导致不可重用的设计方案。
- 设计模式使得设计方案更加灵活,且易于修改。在很多设计模式中广泛使用了开闭原则、依赖倒转原则、迪米特法则等面向对象设计原则,使得系统具有较好的可维护性,真正实现可维护性的复用。在软件开发中合理使用设计模式,可以使得系统中的一些组成部分在其他系统中得以重用,而且在此基础上进行二次开发很方便。正因为设计模式具有该优点,因此在JDK类库、.NET Framework SDK、Struts、Spring等类库和框架的设计中大量使用了设计模式。
- 设计模式的使用将提高软件系统的开发效率和软件质量,且在一定程度上节约设计成本。设计模式是一些通过多次实践得以证明的行之有效的解决方案,这些解决方案通常是针对某一类问题最佳的设计方案,因此可以帮助设计人员构造优秀的软件系统,并可直接重用这些设计经验,节省系统设计成本。
- 设计模式有助于初学者更深入地理解面向对象思想,一方面可以帮助初学者更加方便地阅读和学习现有类库(如JDK)与其他系统中的源代码;另一方面还可以提高软件的设计水平和代码质量。
3、小结
- 模式是在特定环境中解决问题的一种方案。
- GoF(Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides)最先将模式的概念引入软件工程领域,他们归纳发表了23种在软件开发中使用频率较高的设计模式,旨在用模式来统一沟通面向对象方法在分析、设计和实现间的鸿沟。
- 软件模式是将模式的一般概念应用于软件开发领域,即软件开发的总体指导思路或参照样板。软件模式可以认为是对软件开发这一特定“问题”的“解法”的某种统一表示,即软件模式等于一定条件下出现的问题以及解法。
- 设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结,使用设计模式是为了可重用代码、让代码更容易被他人理解、提高代码的可靠性。
- 设计模式一般有如下几个基本要素:模式名称、问题、目的、解决方案、效果、实例代码和相关设计模式,其中的关键元素包括模式名称、问题、解决方案和效果。
- 设计模式根据其目的可分为创建型、结构型和行为型三种;根据范围可分为类模式和对象模式两种。
- 设计模式是从许多优秀的软件系统中总结出的成功的、能够实现可维护性复用的设计方案,使用这些方案将避免我们做一些重复性的工作,而且可以设计出高质量的软件系统。