系统架构设计师:设计模式概述
面向对象技术为软件技术带来新的发展。人们运用面向对象的思想分析系统、为系统建模并设计系统,最后使用面向对象的程序语言来实现系统。
但是面向对象的设计并不是一件很简单的事情,尤其是要设计出架构良好的软件系统更不容易。
为了提高系统的复用性,需要进行一些“额外”的设计(这里的额外并不是无用的,而是指业务领域之外),定义类的接口、规划类的继承结构、建立类与类之间的关系。
毋庸置疑,良好的设计可以让系统更容易地被复用、被移植和维护,而如何快速进行良好的设计则是设计模式要讨论的问题。
设计模式是软件架构设计师的必修课,设计模式中蕴含的思想是架构设计师必须掌握的。
一、设计模式概述
在 20 世纪 70 年代,Christopher Alexander 提出了城市建筑的模式,他认为:模式就是描述一个不断发生的问题和该问题的解决方案。
随后,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 写了一本著名的参考书《设计模式:可复用面向对象软件的基础》。
后人也因为这本书称这四个人为四人组,将这本书中描述的模式称为 GoF(Gang of Four)设计模式。
在这本书中,四人组将设计模式定义为:对被用来在特定场景下解决一般设计问题的类和互相通信的对象的描述。
通俗地说,可以把设计模式理解为对某一类问题的通用解决方案。
(一)设计模式的概念
设计模式旨在解决特定类型的问题。例如,工厂模式是为了解决类创建的问题,而适配器模式则是为了解决接口不匹配的问题。如果将解决A问题的设计模式使用在B问题上,结果肯定是不合适的。因此,在描述设计模式前,首先要清楚这个设计模式到底要解决什么样的问题。
设计模式提供了一套通用的解决方案,并非具体的实现方式。虽然GoF的书中主要使用C++来描述这些模式,但它们的思想可以适用于Java甚至非面向对象的语言。具体应用时可以根据实际情况进行相应的变化,比如,对于工厂模式就有很多种变化。
尽管设计模式的概念由GoF首次系统性地提出,但这些模式并非他们原创。实际上,这些模式是从众多成功的项目设计中抽象和提炼出来的。
学习设计模式不仅仅是学习模式本身,更重要的是理解模式中的思想。设计模式的目标是提高软件架构的质量。虽然设计模式中描述的大多是面向对象的低层设计方案,但其中包含的却是更广泛的软件设计理念,与软件架构风格相辅相成,如MVC框架既可视为一种设计模式也可看作一种架构风格。
设计模式应在适当的场景下使用,滥用会导致负面效果。首先,设计模式有其适用的场合,不合适的情况下使用会有害无益;其次,过度依赖设计模式可能不会增加软件的复用性,反而会使系统变得复杂且难以维护。因此,架构设计师需要明智地决定何时以及如何使用设计模式。
(二)设计模式的组成
在描述一个设计模式时,至少需要包含四个方面:模式名称、问题、解决方案、效果。这四个方面就是设计模式的四要素。
模式名称
每种设计模式都有自己的名字,也就是模式名称。名不正则言不顺,一个明确的名称有助于架构设计师快速理解模式的核心用途和适用场景。
问题
设计模式都有其应用的场合,即该设计模式意图解决的问题。超出了这个问题就不应该再应用这种模式,因此问题是设计模式的第二要素。正确识别模式所针对的具体问题对于避免误用至关重要。
解决方案
设计模式的目的就是解决问题,所以在描述设计模式时当然要有解决问题的方法描述。这是设计模式的另外一个要素——解决方案。它详细说明了如何使用特定的设计模式来应对已定义的问题。
效果
虽然架构设计师知道应用设计模式可以提高架构质量,提高软件的复用性,但对于每一种设计模式而言,还有其更具体的效果描述。所以设计模式的最后一个要素就是效果,它展示了采用该设计模式可能带来的正面或潜在负面的影响。
这四个要素是描述设计模式时必不可少的部分。
(三)GoF 设计模式
GoF(Gang of Four)的著作不仅首次总结了设计中的常用模式,还在学术上建立了软件设计模式的地位。因此,人们习惯上将GoF提出的23个模式统称为GoF模式。
(1)Factory Method模式
Factory Method模式提供了一种延迟创建类的方法,使用这个方法可以在运行期由子类决定创建哪一个类的实例。
(2)Abstract Factory模式
Abstract Factory模式,又称为抽象工厂模式,主要为解决复杂系统中对象创建的问题。它提供了一个一致的对象创建接口来创建一系列具有相似基类或相似接口的对象,是一种很有代表性的设计模式。
(3)Builder模式
Builder模式与Abstract Factory模式非常类似,但Builder模式是逐步地构造出一个复杂对象,并在最后返回对象的实例。它可以将复杂对象的创建与表示分离,使得同样的创建过程可以创建不同的表示。
(4)Prototype模式
Prototype模式可以根据原型实例制定创建的对象种类,并通过深复制这个原型来创建新的对象。它有着同Abstract Factory模式和Builder模式相同的效果,但在需要实例化的类是在运行期才被指定且要避免创建一个与产品曾是平行的工厂类层次时更为灵活。
(5)Singleton模式
Singleton模式也是一种很有代表性的模式,使用它可以保证一个类仅有一个实例,从而可以提供一个单一的全局访问点。
(6)Adapter模式
Adapter模式可以解决系统间接口不相容的问题。通过Adapter可以把类的接口转化为客户程序所希望的接口,从而提高复用性。
(7)Bridge模式
Bridge模式把类的抽象部分同实现部分相分离,这样类的抽象和实现都可以独立地变化。
(8)Composite模式
Composite模式提供了一种以树形结构组合对象的方法,使用它可以使得单个对象和组合后的对象具有一致性以提高软件的复用性。
(9)Decorator模式
Decorator模式可以动态地为对象的某一个方法增加更多的功能,在很多情况下使用它可以不必继承出新的子类从而维护简洁的类继承结构。
(10)Facade模式
Facade模式为一组类提供了一致的访问接口。使用Facade可以封装内部具有不同接口的类,使其对外提供统一的访问方式。
(11)Flyweight模式
Flyweight模式可以共享大量的细粒度对象,从而节省创建对象所需要分配的空间,不过在时间上的开销会变大。
(12)Proxy模式
顾名思义,Proxy模式为对象提供了一种访问代理,通过对象Proxy可以控制客户程序的访问,例如:访问权限的控制、访问地址的控制、访问方式的控制等。
(13)Interpreter模式
定义了一个解释器,来解释遵循给定语言和文法的句子。
(14)Template Method模式
定义一个操作的模板,其中的一些步骤会在子类中实现,以适应不同的情况。
(15)Chain of Responsibility模式
Chain of Responsibility模式把可以响应请求的对象组织成一条链,并在这条对象链上传递请求,从而保证多个对象都有机会处理请求而且可以避免请求方和相应方的耦合。
(16)Command模式
将请求封装为对象,从而增强请求的能力,如参数化、排队、记录日志等。
(17)Iterator模式
Iterator模式提供了顺序访问一个对象集合中的各元素的方法,使用Iterator可以避免暴露集合中对象的耦合关系。
(18)Mediator模式
Mediator模式可以减少系统中对象间的耦合性。它使用中介对象封装其他的对象,从而使这些被封装的对象间的关系就成了松散耦合。
(19)Memento模式
Memento模式提供了一种捕获对象状态的方法,且不会破坏对象的封装,并且可以在对象外部保存对象的状态,并在需要的时候恢复对象状态。
(20)Observer模式
Observer模式提供了将对象的状态广播到一组观察者的方式,从而可以让每个观察者随时可以得到对象更新的通知。
(21)State模式
State模式允许一个对象在其内部状态改变的时候改变它的行为。
(22)Strategy模式
使用Strategy模式可以让对象中算法的变化独立于客户。
(23)Visitor模式
表示对某对象结构中各元素的操作,使用Visitor模式可以在不改变各元素类的前提下定义作用于这些元素的新操作。
(四)其他设计模式
在GoF之后,人们继续对设计模式进行发掘,总结出更多的设计模式。在J2EE应用领域,人们也对使用J2EE框架开发的应用程序总结出一系列设计模式。因为这些设计模式是同J2EE技术紧密相关的,所以介绍中将会使用一些J2EE技术术语。
(1)Intercepting Filter模式
在J2EE的BPS(Basic Programming System,基本编程系统)应用框架下,在真正响应客户端请求前经常需要进行一些预处理,如客户身份验证、客户Session的合法性验证、字符集转码、客户请求记录等。
当然可以将这些请求预处理在每一个Servlet中,不过这样的话预处理的代码就“侵入”了真正的处理程序,使得代码变得更加难以维护。
Intercepting Filter模式提供了解决这个问题的方法。它通过截取客户请求,并将请求发送到Filter链中,一步一步地进行预处理,直到这些处理结束,请求才会被转发到真正响应客户请求的Servlet中。
(2)Session Facade模式
Session Facade模式广泛应用于EJB开发的J2EE应用程序中。EJB是一种分布式构件,EJB的客户端需要通过EJB容器调用EJB,即使EJB的客户端同EJB部署于同一台机器,对EJB的调用也许要通过网络接口进行远程调用。
因此,在开发EJB时,需要尽量减少对EJB调用的次数以提高性能。同时为了提高EJB构件的可维护性和复用性,应该尽量将EJB构件的接口设计得一致。
在GoF设计模式中就有Facade模式提高接口的一致性,在J2EE开发领域,人们把Session Bean和Facade模式结合起来,封装业务逻辑的接口,形成了Session Facade模式。这样不仅提高了系统的性能,还增强了系统的可维护性和复用性。
(五)设计模式与软件架构
软件架构描述了软件的组成,例如,经典的“4+1”视图,将软件架构通过逻辑视图、开发视图、进程视图、物理视图及场景视图来进行描述。在这些视图中,描述了软件系统中类之间的关系、进程之间的关系、软件和硬件的结合等问题。一般来说,软件架构更倾向于从整体和全局上描述软件的组成。
而设计模式则更侧重于类与类、对象与对象之间的关系。例如在逻辑视图中,可以使用多种设计模式来组织类与类之间的关系。因此,有很多人认为,设计模式和软件架构是面向不同层次问题的解决方案。
同设计模式一样,软件架构也有一些固定的模式,通常称为架构风格。常见的架构风格有分层架构、客户端—服务器架构、消息总线、面向服务的架构(Service-Oriented Architecture,SOA)等。
软件架构风格同设计模式在某种含义上是一致的。设计模式和软件架构中蕴含的很多思想是一致的。无论是架构风格还是设计模式,人们在追求良好设计的过程中,将一些常见解决方案总结、整理出来,形成固定的风格与模式。例如消息总线的架构风格同Observer模式就有神似之处。
因此,掌握设计模式对于软件架构设计有非常大的帮助。通过理解并应用这些模式和风格,开发者能够在不同的抽象层次上构建更加健壮、灵活和易于维护的系统。
(六)设计模式分类
可以说,设计模式是面向问题的,即每一种设计模式都是为了解决一种特定类型的问题。因此,根据设计模式要解决的问题将设计模式分为三类,分别为创建型、结构型和行为型。
事实上,面向对象的设计中,需要解决的就是:如何管理系统中的对象、如何组织系统中的类与对象、系统中的类与对象如何相互通信。这三类设计模式分别解决了这三个方面的问题。
创建型设计模式主要解决对象创建的问题。在最简单的情况下,在程序中定义类,在使用时创建一个对象实例。但在实际开发中,对象的创建会变得复杂很多,这时就需要使用创建型设计模式解决创建对象的问题。
随着开发系统的不断扩张,系统功能更加丰富,模块之间的复用越来越多,系统中类与对象的结构变得更加复杂。如果缺乏良好的设计,这些类之间的关系将会变得非常混乱。结构型设计模式就是为了解决这些问题的。
除了这种分类方法外,GoF还提出了可以根据设计模式主要应用于类还是对象来对设计模式进行分类,对于这种分类方法就不再赘述了。
GoF 模式分类:
应用范围 | 创建型 | 结构型 | 行为型 |
---|---|---|---|
应用于类 | Factory Method | Adapter | Interpreter Template Method |
应用于对象 | Abstract Factory Builder Prototype Singleton | Adapter Bridge Composite Decorator Facade Flyweight Proxy | Chain of Responsibility Command Iterator Mediator Memento Observer State Strategy Visitor |
随着 GoF 设计模式的提出,后人也总结出了更多的良好设计的范本,并根据其他的方法进行分类。
例如,在《Core J2EE Patterns》一书中,作者将书中列举的 Design Pattern 分为表现层模式、业务层模式和综合层模式。
根据这种分类方法,可以得到应用于 J2EE 框架的设计模式图谱,如表 所示。
表现层 | 业务层 | 综合层 |
---|---|---|
Intercepting Filter | Business Delegate | Data Access Object |
Front Controller | Value Object | Service Activator |
View Helper | Session Facade | |
Composite View | Composite Entity | |
Service to Worker | Value Object Assembler | |
Dispatcher View | Value List Handler | |
Service Locator |