模式是指从某个具体的形式中得到的一种抽象,在特殊的非任意性的环境中,该形式不断地重复出现。软件架构的模式描述了一个出现在特定设计语境中的特殊的再现设计问题,并为它的解决方案提供了一个经过充分验证的通用图示。解决方案图示通过描述其组成构件及其责任和相互关系以及它们的协作方式来具体指定
设计模式的组成
一般来说,一个模式有4个基本成分,分别是模式名称、问题、解决方案和效果
(1)模式名称。每个模式都有一个名字,帮助我们讨论模式和它所给出的信息。模式名称通常用来描述一个设计问题、它的解法和效果,由一到两个词组成。模式名称的产生使我们可以在更高的抽象层次上进行设计并交流设计思想。
(2)问题问题告诉我们什么时候要使用设计模式、解释问题及其背景。例如,MVC(Model-View-Controler,模型-视图-控制器)模式关心用户界面经常变化的问题。它可能描述诸如如何将一个算法表示成一个对象这样的特殊设计问题。在应用这个模式之前,也许还要给出一些该模式的适用条件。
(3)解决方案。解决方案描述设计的基本要素,即它们的关系、各自的任务以及相互之间的合作。解决方案并不是针对某一个特殊问题而给出的。设计模式提供有关设计问题的一个抽象描述以及如何安排这些基本要素以解决问题。一个模式就像一个可以在许多不同环境下使用的模板,抽象的描述使我们可以把该模式应用于解决许多不同的问题。
模式的解决方案部分给出了如何解决再现问题,或者更恰当地说是如何平衡与之相关的强制条件。在软件架构中,这样的解决方案包括如下两个方面。第一,每个模式规定了一个特定的结构,即元素的一个空间配置。例如,MVC模式的描述包括以下语句:“把一个交应用程序划分成三个部分分别是处理输入和输出”第二,每个模式规定了运行期间的行为。例如,MVC模式的解决方案部分包括以下陈述:“控制器接收输入,而输入往往是鼠标移动、点击鼠标按键或键盘输入等事件。事件转换成服务请求,这些请求再发送给模型或视图”。
解决方案不必解决与问题相关的所有强制条件,而是可以集中于特殊的强制条件,对于剩下的强制条件进行部分解决或完全不解决,特别是强制条件相互矛盾时。
(4)效果。效果描述应用设计模式后的结果和权衡。比较与其他设计方法的异同得到应用设计模式的代价和优点。对于软件设计来说,通常要考虑的是空间和时间的权衡。也会涉及到语言问题和实现问题。对于一个面向对象的设计而言,可重用性很重要效果还包括对系统灵活性、可扩充性及可移植性的影响。明确看出这些效果有助于理解和评价设计模式。
模式和软件架构
判断模式取得成功的一个重要准则是它们在多大程度上达到了软件工程的目标。模式必须支持复杂的、大规模系统的开发、维护以及演化。
1.模式作为架构构造块
已经知道,在开发软件时,模式是处理受限的特定设计方面的有用构造块。因此对软件架构而言,模式的一个重要目标就是用已定义属性进行特定的软件架构的构造例如,MVC模式提供了一个结构,用于交互应用程序的用户界面的裁剪软件架构的一般技术,例如使用面向对象特征(如继承和多态性),并没有针对特定问题的解决方案。绝大多数现有的分析和设计方法在这一层次也是失败的。它们仅仅提供构建软件的一般技术,特定架构的创建仍然基于直觉和经验。模式使用特定的面向问题的技术来有效补充这些通用的与问题无关的架构技术。注意,模式不会舍弃软件架构的现有解决方案,相反,它们填补了一个没有被现有技术覆盖的缺口。
2.构造异构架构
单个模式不能完成一个完整的软件架构的详细构造,它仅仅帮助设计师设计应用程序的某个方面。然而,即使正确设计了这个方面,整个架构仍然可能达不到期望的所有属性。为了整体上达到软件架构的需求,需要一套丰富的涵盖许多不同设计问题的模式可获得的模式越多,能够被适当解决的设计问题也会越多,并且可以更有力地支持构造带有已定义属性的软件架构。
为了有效使用模式,需要将它们组织成模式系统。模式系统统一描述模式,对它们分类,更重要的是,说明它们之间如何交互。模式系统也有助于设计师找到正确的模式来解决一个问题或确认一个可选解决方案。这和模式目录相反,在模式目录中每个模式描述的多少与别的模式无关。
3.模式和方法
好的模式描述也包含它的实现指南,可将其看成是一种微方法,用来创建解决一个特定问题的方案。通过提供方法的步骤来解决软件开发中的具体再现问题,这些微方法补充了通用的但与问题无关的分析和设计方法。
4.模式的实现
从模式与软件架构的集成中产生的另一个方面是用来实现这些模式的一个范例。目前的许多软件模式具有独特的面向对象风格。所以,人们往往认为,能够有效实现模式的唯一方式是使用面向对象编程语言,其实不然。
一方面,许多模式确实使用了诸如多态性和继承性等面向对象技术。策略模式和代理模式是这种模式的例子;另一方面,面向对象特征对实现这些模式并不是最重要的。例如,在C语言中实现策略模式可以通过采用函数指针来代替多态性和继承性。
在设计层次,大多数模式只需要适当的编程语言的抽象机制,如模块或数据抽象。因此,可以用几乎所有的编程范例并在几乎所有的编程语言中来实现模式。另外,每种编程语言都有它自己特定的模式,即语言的惯用法。这些惯用法捕获了现有的有关该语言的编程经验并为它定义了一个编程风格。
设计模式的分类
- Coad 的面向对象模式
Peter Coad从MVC的角度对面向对象系统进行了讨论,设计模式由最底层的构成部分(类和对象)及其关系来区分。他使用了一种通用的方式来描述一种设计模式,将模式划分为以下三类。
(1)基本的继承和交互模式。主要包括OOP 所提供的基本建模功能,继承模式声明了类能够在其子类中被修改或被补充,交互模式描述了在有多个类的情况下消息的传递。
(2)面向对象软件系统的结构化模式。描述了在适当情况下,一组类如何支持面向对象软件架构的建模。主要包括条目 (item)描述模式、为角色变动服务的设计模式和处理对象集合的模式
(3)与MVC框架相关的模式。几乎所有由 Peter Coad 提出的模式都指明了如何构造面向对象的软件,有助于设计单个的或者一小组构件,描述了 MVC框架的各个方面但是,他没有重视抽象类和框架,没有说明如何改造框架
2.代码模式
代码模式的抽象方式与OOP语言中代码规范很相似,该类模式有助于解决某种OOP语言中的特定问题。代码模式的主要目标如下
(1)指明结合基本语言概念的可用方式
(2)构成源码结构与命名规范的基础。
(3)避免OOP 语言(尤其是 C++语言)的缺陷。
代码模式与具体的程序设计语言或者类库有关,它们主要从语法的角度对于软件系统的结构方面提供一些基本的规范。这些模式对于类的设计不适用,同时也不支持程序员开发和应用框架,命名规范是类库中名字标准化的基本方法,以免在使用类库时产生混淆。
3.框架应用模式
在应用程序框架“菜谱”(application framework cookbook recipes)中有很多“菜谱条”,它们用一种不是很规范的方式描述了如何应用框架来解决特定的问题。程序员将框架作为应用程序开发的基础,特定的框架适用于特定的需求。“莱谱条”通常并不讲解框架的内部设计实现,只讲如何使用。
4.形式合约
形式合约(formal contracts)也是一种描述框架设计的方法,强调组成框架的对象间的交互关系。有人认为它是面向交互的设计,对其他方法的发展有启迪作用。但形式化方法由于其过于抽象,而有很大的局限性,仅在小规模程序中使用形式合约模式有如下优点。
(1)符号所包含的元素很少,并且其中引入的概念能够被映射成为面向对象程序设计语言中的概念。例如,参与者映射成为对象。
(2)形式合约中考虑到了复杂行为是由简单行为组成的事实,合约的修订和扩充操作使得这种方法很灵活,易于应用。
形式合约模式有以下三个缺点
(1)在某些情况下很难用,过于繁琐。若引入新的符号,则又使符号系统复杂化。
(2强制性的要求过分精密,从而在说明中可能发生隐患(例如几余)。
(3)形式合约的抽象程度过低,接近 OOP 语言,不易分清主次。
5.设计模式目录的内容
Erich Gamma在他的博士论文中总结了一系列的设计模式,做出了开创性的工作。他用一种类似分类目录的形式将设计模式记载下来,我们称这些设计模式为设计模式目录。根据模式的目标(所做的事情),可以将它们分成创建性模式 (creational)、结构性模式(structural)和行为性模式(behavioral)。创建性模式处理的是对象的创建过程,结构性模式处理的是对象/类的组合,行为性模式处理类和对象间的交互方式和任务分布。根据它们主要的应用对象,又可以分为主要应用于类的和主要应用于对象的。
下表是ErichGamma等人总结的23种设计模式,这些设计模式通常被称为GoF (Gang of Four,四人组)模式。因为这些模式是在Design Patterns: Elements of Reusable Object-Oriented Software 一书中正式提出的,而该书的作者是 Erich Gamma、Richard Helm、Ralph Johnson和John Vlissides,这几位作者常被称为“四人组”。
分类 | 设计模式 | 简要说明 | 可改变方面 |
创建型 | Abstract Factory 抽象工厂模式 | 提供一个接口,可以创建一系列相关或相互依赖的对象,而无须指定它们具体的类 | 产品对象族 |
Builder 生成器模式 | 将一个复杂类的表示与其构造相分离,使得相同的构建过程能够得出不同的表示 | 如何建立一种组合对象 | |
Factory Method 工厂方法模式 | 定义一个创建对象的接口,但由子类决定需要实例化哪一个类。工厂方法使得子类实例化的过程推迟 | 实例化子类的对象 | |
Prototype 原型模式 | 用原型实例指定创建对象的类型,并且通过复制这个原型来创建新的对象 | 实例化类的对象 | |
Singleton 单子模式 | 保证一个类只有一个实例,并提供一个访问它的全局访问点 | 类的单个实例 | |
结构型 | Adapter 适配器模式 | 将一个类的接口转换成用户希望得到的另一种接口。它使原本不相容的接口得以协同工作 | 与对象的接口 |
Bridge 桥模式 | 将类的抽象部分和它的实现部分分离开来,使它们可以独立地变化 | 对象的实现 | |
Composite 组合模式 | 将对象组合成树型结构以表示“整体-部分”的层次结构,使得用户对单个对象和组合对象的使用具有一致性 | 对象的结构和组合 | |
Decorator 装饰模式 | 动态地给一个对象添加一些额外的职责。它提供了用子类扩展功能的一个灵活的替代,比派生一个子类更加灵活 | 无子类对象的责任 | |
Facade 外观模式 | 定义一个高层接口,为子系统中的一组接口提供个一致的外观,从而简化了该子系统的使用 | 与子系统的接口 | |
Flyweight 享元模式 | 提供支持大量细粒度对象共享的有效方法 | 对象的存储代价 | |
Proxy 代理模式 | 为其他对象提供一种代理以控制这个对象的访问 | 如何访问对象,对象位置 | |
行为型 | Chain of Responsibility职责链模式 | 通过给多个对象处理请求的机会,减少请求的发送者与接收者之间的耦合。将接收对象链接起来,在链中传递请求,直到有一个对象处理这个请求 | 可满足请求的对象 |
Command 命令模式 | 将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化,将请求排队或记录请求日志,支持可撤销的操作 | 何时及如何满足一个请求对象 | |
lterator 迭代器模式 | 提供一种方法来顺序访问一个聚合对象中的各个元素,而不需要暴露该对象的内部表示 | 如何访问、遍历聚合的元素 | |
Mediator 中介者模式 | 用一个中介对象来封装一系列的对象交互。它使各对象不需要显式地相互调用,从而达到低耦合还可以独立地改变对象间的交互 | 对象之间如何交互及哪些对象交互 | |
Memento 备忘录模式 | 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,从而可以在以后将该对象恢复到原先保存的状态 | 何时及哪些私有信息存储在对象之外 | |
Observer 观察者模式 | 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新 | 依赖于另一对象的数量 | |
State 状态模式 | 允许一个对象在其内部状态改变时改变它的行为 | 对象的状态 | |
Strategy 策略模式 | 定义一系列算法,把它们一个个封装起来,并且使它们之间可互相替换,从而让算法可以独立于使用它的用户而变化 | 算法 | |
Template Method*模板模式 | 定义一个操作中的算法骨架,而将一些步骤延迟到子类中,使得子类可以不改变一个算法的结构即可重新定义算法的某些特定步骤 | 算法的步骤 | |
Visitor 访问者模式 | 表示一个作用于某对象结构中的各元素的操作,使得在不改变各元素类的前提下定义作用于这些元素的新操作 | 无需改变其类而可应用于对象的操作 |