软件设计模式
设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、设计经验的总结。它对于指导程序开发人员提高服装管理软件等软件系统的质量,建立灵活、高效的软件框架具有十分重要的作用。在本书中,我们将其理解为程序设计级别的软件复用技术。本书重点介绍了DesignPatterns:ElemensofReusalbeObject—OrientedSoftware一书中的23种GOF(GangofFour,即对ErichGamma,RichardHelm,RalphJohnson&JohnVlissides四位作者的呢称)设计模式。
软件设计模式基础
7.1.1什么是设计模式
关于模式这一概念,最早出现在城市建筑领域,ChristopherAlexander的一本关于建筑的书T^eTimelessWayofBuilding中明确给出了模式的概念,他说:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心,这样你就能一次又一次地使用该方案而不必做重复劳动”,他使用模式这一概念来解决建筑中的一些问题,现在这一概念逐渐被计算机科学所采纳。在计算机科学中,从代码重用角度讲,设计模式是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。从整体抽象的层次上讲,设计模式通常是对于某一类软件设计问题的可重用的解决方案。在软件工程中一个设计模式能解决一类设计问题。每一个设计模式系统地命名、解释和评价了面向对象系统中一个重要的和重复出现的设计。通过设计模式,定义了一个特定的面向对象设计过程中所包含的类和实例、它们的角色、协作方式以及职责分配,确认了该设计中的设计要点。本书将从该层次上介绍设计模式。将设计模式引入软件开发和软件过程,其目的是充分利用已有的软件开发和设计经验,而不必使人们总要从头开始解决相类似的问题。
通常,一个设计模式有4个基本要素:
(1)模式名称(PatternName)。模式名称主要用来描述该模式的问题、解决方案和效果。基于统一的模式词汇表或模式命名规则,软件设计者之间不必花时间了解另外一个设计者的模式名称的由来,就可以理解另外一名设计者的设计思想,方便了设计者间的交流。
(2)问题(Problem)。问题描述了使用该设计模式必须满足的一些先决条件。它解释了该设计模式存在的背景,它可能描述特定的设计问题,也可能描述某个抽象领域的设计问题,也可能描述了设计不灵活的类或是对象结构。
(3)解决方案(Solution)。解决方案描述了一个设计的各个设计构件,它们之间的相互关系以及各自的职责和协作方式。
(4)效果(Consequences)。该要素描述了设计模式的使用效果,以及该模式中要注意的问题。
7.1.2设计模式的作用
有过服装管理软件等软件开发经验的读者应该有这样的体会:开发一个系统最困难的不在于编码,而在于系统的设计。设计是软件开发生命周期中很重要的一环,好的设计能带来好的产品,而设计不好,就相当于是大楼的地基没打好,这种后果对软件开发是灾难性的。如果能将一些经验丰富的设计者的设计思想整理成一套切实可行的设计模式,那么就可以有效地避免这些问题,设计模式的作用具体体现在以下几个方面:
第一,设计模式有利于促进基于模式的开发方法。设计模式是面向对象软件设计的思想精髓,是拥有多年开发设计经验的人的经验传承,是专家的建议集合,是论证后切实可行的一套完整的设计方案。将这些宝贵的经验模式化,使得新的设计者能够直接复用这些经验,而不需要从头至尾重新设计,更不必使得设汁者开发出一个设计方案后.花费数月甚至数年的时间去验证该设计方案的可行性,这将大大地提高软件的开发效率。
第二,设计模式是一套经过证实的可复用的成功的设计和体系结构。这使得新的系统开发者更加容易理解合作者的设计思路,在一定程度上解决了设计者之间的思维差异问题,使得设计者之间有了一个共同的交流平台,帮助设计者做出有利于系统复用的选择。
第三,设计模式有利于提高设计质量。对需求或开发背景相同的软件开发,基于一个统一的设计模式,将有利于设计的规范化和标准化。
7.1.3设计模式的描述
描述设计模式有一套统一的格式,每一个设计模式根据以下内容分成若干个模板,每个模板具有统一的信息描述结构。主要描述结构如下:
模式名:模式名简单描述了设计模式的本质,主要用于唯一地标识模式。
意图:阐述设计模式的基本原理、目的和作用。
别名:模式的辅助名称。
动机:描述了具体的通过类、对象解决特定设计问题的情景。
适用性:限制了应用设计模式的场景。
结构:用类图描述模式中的各个类,它们的结构以及它们之间的静态关系;用对象图描述运行时刻特定的对象结构;用交互图说明对象之间的请求序列和协作关系。
参与者:确定设计模式中的类或对象以及它们各自的职责。
协作:定义了参与者之问的协作顺序。
效果:分析了使用设计模式后的效果以及权衡取舍的过程。
实现:说明了实现模式时需要知道的一些提示、技术要点及应避免的缺陷,以及是否存在某些特定于实现语言的问题。
代码示例:示范通过C++或Smalltalk实现某个设计模式的代码片段。
已知应用:实际系统中模式应用的例子。
相关模式:列举与各模式紧密相关的模式以及它们之间的区别。
7.1.4如何使用设计模式
通过学习设计模式,可以使软件开发人员的面向对象分析和设计的能力得到很大的拓展和加强,即使编程人员还没有直接使用设计模式,只要真正用心理解了设计模式,那么服装管理软件等软件开发人员的设计水平也将得到很大的提高。当然,学习设计模式最主要的目的是为了应用。那么如何使用设计模式呢?下面就是使用设计模式时应该遵循的几条准则:
准则一:以充分学习和了解各个设计模式为基础。只有充分了解和掌握了每一个设计模式背后的设计原则和策略,才有可能运用自如。
准则二:设计模式应该互相配合,共同解决问题。不能将设计模式作为一个单独的东西使用,应该将它们结合起来。
准则三:重点思考和学习模式背后的原则和策略,而不仅仅是学习和运用已有的模式,应该能创造自己的模式。
在使用设计模式的过程中,使用者可能会产生以下几个误区:
误区一:在使用设计模式时,因为软件中的设计模式最初是以设计模式为名引入的,所以,学习者误以为模式只能应用于软件开发的设计阶段。其实不然,在软件开发的各个阶段,包括分析、设计和实现阶段都存在模式。
误区二:在项目开发的过程中,试图使用所有的模式。实际上,在项目的开发过程中,并不是模式使用得越多就一定越好。如果软件开发人员不能根据特定的问题,去寻求模式的解决方案,而只是凭臆想或是过于牵强地加模式,有可能使项目最后偏离了方向,使得整个项目真正需要解决的问题没有解决,反而在一些不重要的额外问题上花费过多的时间和精力,甚至使得最后的软件因为过于灵活,而没有人真正需要使用它。另外,很多模式是关于扩展性和重用性的。当确实需要扩展性的时候,模式提供某种方法来实现它,这可以有效地提高软件开发人员的开发效率,但是当不需要它的时候,应该让设计保持简单并且不要添加不需要的抽象层。
误区三:在不理解项目的实际背景的情况下,就急于照本宣科似的应用设计模式。
ErichGamma(里程碑式的书籍《设计模式》的作者之一)在有关设计模式的使用方法上,就建议人们“不要一开始就马上把模式套进某个设计,而是当你一边深入并且对问题理解更多的时候才使用它们”。
2设计模式的分类
设计模式的分类有很多种方法,本文主要介绍GOF(GangofFour)设计模式分类和POSA(Pattern—OrientedSoftwareArchitectur,面向模式的软件架构)设计模式分类。
7.2.1GOF设计模式分类
20世纪90年代,ErichGamma等4人,也就是通常所说的GOF(GangofFour,“四人帮”)从建筑设计领域将设计模式引入到计算机科学领域时,根据以下两条准则对设计模式进行了分类。第一,根据目的准则,设计模式可分为创建型(Creational)、结构型(Structural)、行为型(Behavioral)三种;第二,根据范围准则,设计模式可分为类模式和对象模式,类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻已经确定下来了。对象模式处理对象之间的关系,这些关系在运行时刻是可以变化的,具有动态性。依据以上两种分类准则,表7.1形象地表示出了设计模式的分类。
创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。
结构型类模式使用继承机制类组合类,而结构型对象模式则描述了对象的组装方式。
行为型类模式使用继承描述算法和控制流,而行为型对象模式则描述一组对象怎样协作完成单个对象所无法完成的任务。
有关创建型(Creational)设计模式、结构型(Structural)设计模式、行为型(Behavioral)设计模式以及各自属于这三种设计模式中的模式,将分别在后面几节中给予介绍。
7.2.2POSA模式分类
FrankBuschmann等编著的Pattern-OrientedSoftwareArchitecture是有关模式方面的又一经典著作,在此书中,作者拓展了模式的范围,根据规模和抽象层次的不同,定义了三个类别或层次的设计模式:体系结构模式(ArchitecturalPatterns)、设计模式(DesignPatterns)、惯用法模式(Idioms)。体系结构模式表达了软件系统的基本结构组织形式或者结构方案,它包含一组预定义的子系统,规定了这些子系统的责任,同时还提供了用于组织和管理这些子系统的规则和向导;设计模式描述了在特定环境下,用于解决通用软件设计问题的组件以及这些组件相互通信时的可重现结构,为软件系统的子系统、组件或者组件之间的关系提供一个精炼之后的解决方案;惯用法模式描述了如何实现组件的某些功能,或者利用编程语言的特性来实现组件内部要素之间的通信功能,它是一个与编程语言相关的低级模式,惯用法模式主要捕获了很多编程经验。
创建型(Creational)设计模式
创建型模式与对象的创建有关,即描述怎样创建一个对象,它隐藏对象创建的具体细节,使程序代码不依赖具体的对象。因此当增加一个新对象时几乎不需要修改代码即可。创建型类模式将对象的部分创建工作延迟到子类,而创建型对象模式则将它延迟到另一个对象中。创建型类模式有FactoryMethod(工厂方法)模式,创建型对象模式包括AbstractFactory(抽象工厂)模式、Builder(生成器)模式、Prototype(原型)模式、Sindeton(单件)模式4种。
7.3.1FactoryMethod(工厂方法)模式
名称:FactoryMethod
意图:定义一个用于创建对象的接口,让子类决定实例化哪一个类。FactoryMethod使一个类的实例化延迟到其子类。
适用性:
(1)当一个类不知道它所必须创建的对象的类的时候。
(2)当一个类希望由它的子类来指定它所创建的对象的时候。
(3)当类将创建对象的职责委托给多个帮助子类中的某一个,并且希望将哪一个帮助子类是代理者这一信息局部化的时候。
7.3.2AbstractFactory(抽象工厂)模式
名称:AbstractFactory
意图:提供一个创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类。
结构:如图7.2所示:
适用性:
(1)一个系统要独立于它的产品的创建、组合和表示时。
(2)一个系统要由多个产品系列中的一个来配置时。
(3)当要强调一系列相关的产品对象的设计以便进行联合使用时。
(4)当提供一个产品类库,而只想显示它们的接口而不是实现时。
7.3.3BuiIder(生成器)模式
名称:Builder
意图:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
结构:如图7.3所示:
适用性:
(1)当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
(2)当构造过程必须允许被构造的对象有不同的表示时。
7.3.4Prototype(原型)模式
名称:Prototype
意图:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。
结构:如图7.4所示:
适用性:
(1)要实例化的类是在运行时刻指定时,例如,通过动态装载。
(2)当为了避免创建一个与产品类层次平行的工厂类层次时。
(3)一个类的实例只能有几个不同状态组合中的一种时,建立相应数目的原型并复制它们可能比每次用合适的状态手工实例化该类更方便一些。
Prototype设计结构图
Singleton(单件)模式
名称:Singleton
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
结构:如图7.5所示:
适用性:
(1)当类只能有一个实例而且用户可以从一个众所周知的访问点访问它时。
(2)当这个唯一实例是通过子类化可扩展的,并且用户应该无须更改代码就能使用一个扩展的实例的时候。
结构型(Structural)设计模式
结构型模式处理类或对象的组合,即描述类和对象之间怎样组织起来形成大的结构,从而实现新的功能。结构型类模式采用继承机制来组合类,如Adapter(适配器类)模式;结构型对象模式则描述了对象的组装方式,如Adapter(适配器对象)模式、Bridge(桥接)模式、Composite(组合)模式、Decorator(装饰)模式、Facade(外观)模式、Flyweight(享元)模式、Proxy(代理)模式。
7.4。1Adapter(适配器对象)模式
名称:Adapter
意图:将一个类的接口转换成用户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用性:
(1)当软件开发者想使用一个已经存在的类,而它的接口不符合需求的时候。
(2)当软件开发者想创建一个可以复用的类,该类可以与其他不相关的类或不可预见的类(即那些接口可能不一定兼容的类)协同工作时。
(3)(仅适用于对象Adapter)当软件开发者想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口时。
7.4.2Bridge(桥接)模式
名称:Bridge
意图:将抽象部分与它的实现部分分离,使它们都可以独立地变化。
适用性:
(1)当软件开发者不希望在抽象和它的实现部分之问有一个固定的绑定关系时,例如这种情况可能是因为,在程序运行时刻实现部分应可以被选择或者切换。类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。这时Bridge模式使程序员可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
(2)当对一个抽象的实现部分的修改应对用户不产生影响,即用户的代码不必重新编译时。例如,在C++中,类的表示在类接口中是可见的。有许多类要生成。这样一种类层次结构说明必须将一个对象分解成两个部分。Rumbaugh将这种类层次结构称为“嵌套的普化”(NestedGeneralizations)。
(3)当软件开发者想在多个对象间共享实现(可能使用引用计数),但同时要求对用户透明时可采用这种模式。
7.4.3Composite(组合)模式
名称:Composite
意图:将对象组合成树形结构以表示“部分一整体”的层次结构。Composite使得用户对单个对象和组合对象的使用具有一致性。
结构:如图7.8所示:
适用性:表示对象的部分一整体层次结构,使得用户忽略组合对象与单个对象的不同,方便软件开发者统一地使用组合结构中的所有对象。
7.4。4Decorator(装饰)模式
名称:Decorator
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,Decorator模式相比生成子类更为灵活。Decorator模式可以在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责,处理那些可以撤销的职责。
适用性:当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。
7.4.5Facade(外观)模式
名称:Facade
意图:为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性:
(1)当软件开发者要为一个复杂子系统提供一个简单接口时。子系统往往因为不断演化而变得越来越复杂,大多数模式使用时都会产生更多更小的类,这使得子系统更具可重用性,也更容易对子系统进行定制,但这也给那些不需要定制子系统的用户带来一些使用上的困难。Facade模式可以提供一个简单的默认视图,这一视图对大多数用户来说已经足够了,而那些需要更多的可定制性的用户可以越过Facade层。
(2)当用户程序与抽象类的实现部分之间存在着很大的依赖性时,引入Facade将这个子系统与用户以及其他子系统分离,可以提高子系统的独立性和可移植性。
(3)当需要构建一个层次结构的子系统时,使用Facade模式定义子系统中每层的入口点。如果子系统之间是相互依赖的,这时可以让它们仅通过Facade进行通信,从而简化了它们之间的依赖关系。
7.4.6Flyweight(享元)模式
名称:Flyweight
意图:运用共享技术有效地支持大量细粒度的对象。
适用性:当一个应用程序使用了大量的对象的时候,由于使用大量的对象,造成很大的
存储开销。而且由于对象的大多数状态都可变为外部状态,如果删除对象的外部状态,那么
可以用相对较少的共享对象取代很多组对象,应用程序不依赖于对象标识,由于Flyweight
对象可以被共享,对于概念上明显有别的对象,标识测试将返回真值。
7.4.7Proxy(代理)模式
名称:Proxy
意图:为其他对象提供一种代理以控制对这个对象的访问。
结构:如图7.12所示:
图7.12Proxy模式结构图
适用性:在需要用比较通用和复杂的对象指针代替简单的指针的时候,使用Proxy模式。下面是一些可以使用Proxy模式的常见情况:
(1)远程代理(RemoteProxy)为一个对象在不同的地址空间提供局部代表,NEXTSTEP使用NXProxy类实现了这一目的,Coplien称这种代理为“大使”(Ambassador)。
(2)虚代理(VirtualProxy)根据需要创建开销很大的对象。
(3)保护代理(ProtectionProxy)控制对原始对象的访问。当保护代理用于对象,而且有不同的访问权限的时候。例如,在Choices操作系统中,KemelProxies为操作系统对象提供了访问保护。
(4)智能指引(SmartReference)取代了简单的指针,它在访问对象时执行一些附加操作。它的典型用途包括:对指向实际对象的引用计数,这样当该对象没有引用时,可以自动释放它。当第一次引用一个持久对象时,将它装入内存。在访问一个实际对象前,检查是否已经锁定了它,以确保其他对象不能改变它。
行为型(Behavioral)设计模式
行为型设计模式描述算法以及对象之间的任务(职责)分配,它所描述的不仅仅是类或对象的设计模式,还有它们之间的通信模式。这些模式刻画了在运行时刻难以跟踪的复杂的控制流。行为型类模式使用继承机制在类间分派行为,如TemplateMethod(模板方法)模式和Interpreter(解释器)模式;行为型对象模式使用对象复合而不是继承,它描述一组对象怎样协作完成单个对象所无法完成的任务,如ChainofReponsibi_lity(职责链)模式、Command(命令)模式、Iterator(迭代器)模式、Mediator(中介者)模式、Memento(备忘录)模式、Observer(观察者)模式、State(状态)模式、Strategy(策略)模式、Visitor(访问者)模式。
7.5.1TemplateMethod(模板方法)模式
名称:TemplateMethod
意图:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。TemplateMethod使得子类可以不改变一个算法的结构,即可重定义该算法的某些特定步骤。
适应性:当可以一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现的时候。例如,软件开发者可以将各子类中公共的行为提取出来并集中到一个公共父类中以避免代码重复。这是Opdyke和Johnson所描述过的“重分解以一般化”的一个很好的例子。首先识别现有代码中的不同之处,并且将不IN2_处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
7。5.2Interpreter(解释器)模式
名称:Interpreter
意图:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
适应性:当有一个语言需要解释执行,并且可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:对于复杂的文法,文法的类层次变得庞大而无法管理,此时语法分析程序生成器这样的工具是最好的选择。它们无须构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。效率不是一个关键问题,最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转
换成另一种形式。例如,正则表达式通常被转换成状态机,但即使在这种情况下,转换器仍
可用解释器模式实现,该模式仍是有用的。
7.5.3ChainofResponsib…ty(职责链)模式
名称:ChainofResponsibility
意图:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
适应性:当有多个对象可以处理一个请求,哪个对象处理该请求由运行时刻自动确定的时候。
7.5.4Command(命令)模式
名称:Command
意图:将一个请求封装为一个对象,从而使程序员可用不同的请求对用户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
结构:如图7.16所示:
图7.16Command模式结构图
适应性:
(1)当需要在不同的时刻指定、排列和执行请求的时候。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那里实现该请求。
(2)支持取消操作。Command的Execute操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexeeute和Execute来实现重数不限的“取消”和“重做”。支持修改日志,这样当系统崩溃时,这砦修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保存修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用Execute操作重新执行它们。
(3)当需要对事务进行建模的时候,Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得程序员可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。
7.5.5Iterator(迭代器)模式
名称:Iterator
意图:提供一种方法顺序访问一个聚合对象中的各个元素,而又不需暴露该对象的内部表示。
适应性:当访问一个聚合对象的内容而无须暴露它的内部表示的时候,Iterator模式支持对聚合对象的多种遍历,为遍历不同的聚合结构提供一个统一的接口(即支持多态迭代)。
7.5。6Mediator(中介者)模式
名称:Mediator
意图:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
适应性:
(1)当一组对象以定义良好但是复杂的方式进行通信的时候,一个对象引用其他很多对象并且直接与这些对象通信,导致产生的相互依赖关系结构混乱且难以理解,这种情况下采用Mediator模式是一个不错的选择。
(2)当想定制一个分布在多个类中的行为,而又不想生成太多的子类的时候,Mediator模式是一个可利用的好的模式。
7。5.7Memento(备忘录)模式
名称:Memento
意图:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。
适应性:当想把对象恢复到以前某一时刻的状态时,可以使用Memento模式,但事先必须保存一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。如果直接用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
7.5.8Observer(观察者)模式
名称:Observer
意图:定义对象间的一种一对多的依赖关系,对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
适应性:
(1)当一个抽象模型有两个方面,其中一方面依赖于另一方面时。将这二者封装在独立的对象中以使它们可以各自独立地改变和复用。
(2)当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变的时候。
(3)当一个对象必须通知其他对象,而它又不能假定其他对象是谁的情况下,可以使用Observer模式。
7.5.9State(状态)模式
名称:State
意图:允许一个对象在其内部状态改变时改变它的行为。
适应性:当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为的时候。例如,当一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态的时候,这个状态通常用一个或多个枚举常量表示。例如,有多个操作包含这一相同的条件结构时,State模式将每一个条件分支放人一个独立的类中,这使得软件开发者可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
7.5.10Strategy(策略)模式
名称:Strategy
意图:定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。该模式使得算法可独立于使用它的用户而变化。
适应性:
(1)当许多相关类的不同仅仅是因为行为有异引起的时候,Strategy模式提供了一种用多个行为中的一个行为来配置一个类的方法。例如,可能会定义一些反映不同的空间/He间权衡的算法,这就需要使用一个算法的不同变体,当这些变体实现为一个算法的类层次时,就可以使用Strategy模式以避免暴露复杂的、与算法相关的数据结构。
(2)当一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现的时候,可以将相关的条件分支移人它们各自的Strategy模式类中以代替这些条件语句。
7.5.11Visitor(访问者)模式
名称:Visitor
意图:表示一个作用于某对象结构中的各元素的操作。它使程序员可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适应性:
(1)一个对象结构包含很多类对象,它们有不同的接口,当软件开发者想对这些对象实施一些依赖于其具体类的操作的时候。
(2)当软件开发者需要对一个对象结构中的对象进行很多不同的并且不相关的操作,并且需要避免该操作“污染”这些对象的类的时候,Visitor模式使得软件开发者可以将相关的操作集中起来定义在一个类中,当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作,这样使得对象结构的类的定义很少改变,但使用Visitor模式后就经常需要在此结构上定义新的操作,改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价,相比对象结构类经常改变的代价,那么可能还是在这些类中定义这些操作所付出的代价要小得多。
DrawCLI中设计模式的应用
7.6.1DrawCLl基础
DrawCLI是具有可视化编辑容器支持的面向对象的绘图应用程序,因其简单而且通用,一般作为开发工具的样板程序。目前,有各种语言版本的DrawCI。I应用程序,本书主要介绍通过C++语言实现的在MFC平台上的DrawCI。I应用程序。DrawCI。I中有关图元的类及其功能介绍如下:
CDrawObj类(在Drawobj.cppIfl实现)是派生的shape类的基类。该基类处理形状的命中测试、形状的移动和形状的大小调整。通过使用多态性,DrawCLI可以通过CDrawObj的接口与不同类的对象进行交:巨。
CDrawReet类是从CDraw()bj派生的。CDrawRect用于绘制矩形、圆角矩形、椭圆和直线。
CDrawPoly类也是从CDraw()bj派生的,用于绘制多边形。
CDrawOleObj类也是从CDraw()bj派生的,用于表示嵌入的对象。CDrawOleObj将所有特定于ActiveX的操作委托给包含的CDrawItem对象(参阅下面的描述)。对于一般形状操作,对嵌入的对象的处理类似于对DrawCI。I中其他形状对象的处理,因为CDrawOleObj是从CDrawObi派生的。
这些类之间的关系如图7.24所示图元部分类间层次结构图。
DrawCLI中有关图形操作T具的类及其功能介绍如下:
CDrawTool类是所有图形操作丁具对象的基类,其他操作工具类都直接从CDrawTool派生。CDrawTool类定义了工具对象支持的控制接口。
派生。CDrawTool类定义了工具对象支持的控制接口。
CSelectTool类是图元属性选择类。定义了对图元的选中操作。
CRectTool类是矩形工具类,是从CDrawTool类中派生出来的。CRectTool定义了对图元类CDrawRect的操作和控制,通过它可以生成和控制矩形、圆角矩形、椭圆形、直线形状的图元。
CPolyTool类是折线T具类,是从CDrawTool类中派生m来的。CPolyTool定义了对图元类CDrawPoly的操作和控制,通过它可以生成折线形状图形,如五边形等。
以上DrawCLI中4个图形操作T具类之间的层次结构图如图7.25所示:
7.6.2DrawCLI中的设计模式
对DrawCLI中一些比较关键的类以及功能有了较基础的认识之后,接下来分析在DrawCLI中所用到的设计模式。在DrawCLI中,主要用到了常见的三种设计模式,即
Prototype(原型)模式、ChainofReponsibility(职责链)模式和Observer(观察者)模式。
7.6.3Prototype(原型)模式
Prototype设计模式的特点是父类在创建对象时,只是复制一个子类的实例,这个实例被称为Prototype。父类有一个Prototype变量,每个子类都有一个Clone方法,当一个子类需要创建对象时,用户把它作为参数传递给父类的Prototype变量,父类再调用其Clone方法完成对象的创建。
可以看出子类CDrawObj、CDrawPoly、CDrawRect都重载了Clone和Draw方法。不过,这时对Clone方法的调用是在CDrawView中(它就相当于前文所说的“Tool”类)来完成的。当使用者选择一个图形对象时(激活OnLBottonUp消息),程序为了判断这个对象是什么,就复制该对象(通过对CloneSelection的调用),然后到文档类中去找到这个对象。
Prototype设计模式的优点有两个:①可以运行时添加、删除对象。只要被创建对象有Clone方法,需要动态添加一个对象时,只要把该对象作为参数传给“Tool”类,“Tool”类再调用它的Clone方法即可。只要“Tool”类支持Remove方法,用户同样可以动态地删除一个对象。②每个子类只需要一个GraphicTool类,从而大大简化了子类的数量。该设计模式的主要缺点是每个子类必须有Clone方法,但有时候这个要求是达不到的。例如,如果这个子类有一个不支持复制的对象成员变量,对象的构造将会失败。
7.6。40bserver(观察者)
Observer的设计模式描述了一种依赖关系,具体地讲,就是Subject(客体)与Observer(观察者)之间的一对多的关系。一个Subject对应于多个与之相依赖的Observer。当Subject的状态发生变化时,所有的Observer都会得到一个通知(Notify方法)。Observer得到这个通知时,会做相应的改变,以保持和Subject之间的同步。Observer在DrawCLI中的体现就是著名的DocPView(文档/视图)结构,如图7.27所示:
.一般将应用程序的数据保存在Doc类里(在本例中是CDocView),如m—paperColor保存桌布的颜色;m—nMapMode保存映射模式;m—size保
存应用程序的视口尺寸等。一个应用程序只有一个DOC类,如本例中的CDrawDOC,但可以有多个View类,如本例中的CDrawView(很遗憾,DrawCLl只有一个视图类)。View类是人机交互的界面,应用程序在这里接收消息。如果由于种种原因Doc数据发生了改变,那么所有的View都必须被通知到(通过调用Doc类的UpdateAllVJews方法),以便它们能够对所显示的数据进行相应的更新。View在接到来自Doc的通知后,会调用OnUpdate方法。通常,View类的OnUpdate方法要对Doc进行访问,读取Doc的数据,然后再对View的数据成员或控制进行更新,以便反映出Doc的变化。
如图7.27所示,采用Observer设计模式,可以将Subject和Observer之间的耦合抽象化,也可以实现广播通信,但Observer设计模式的最大缺点是会发生用户所不希望的更新。如前所述,由于concretesubject对Concreteobserver的细节一无所知,当不希望某些ConcreteObserver对象更新时,仅靠这种简单的一对多的机制显然是不够的。为了克服这一问题,DrawCLI在OnUpdate方法中加入了一些的判断句。
7.6.5ChainofResponsibiIity(职责链)模式
ChainofResponsibility的主要思想是将消息的处理者按“特殊到一般”的顺序来组织。这里不妨以一个按钮的Help消息为例来说明,假设使用者在anOKButton上单击鼠标,产生了一条Help消息,应用程序是按照如下的顺序处理的:anOKButton先试着处理它,如果能够处理,就弹出一条帮助信息,消息链终止;如果anOKButton不能处理,它就会将这条消息传递给与它上下文最接近的父窗口一一aPrintDialog;如果它有处理方法,则弹出一条关于aPrintDialog的帮助消息,消息链终止;类似地,如果aPrintDialog不能处理,则将Help消息传递给应用程序,应用程序将弹出一条关于该应用的帮助信息,如此形成消息链。
ChainofResponsibility设计模式的优点主要有:
(1)降低了耦合。由于消息链上的发送者和接收者彼此都不必知道对方的细节,而且,
一个消息也不必知道消息链的具体结构,于是发送者和接收者之间,消息和消息链之间的耦
合度就大大降低了。
(2)可以灵活地为对象分配职责。消息链中每个成员只处理它们“分内”的职责,不能处理的部分只要传递给消息链的下一个对象即可。所有设计者可以灵活地限定对象的职责范围,并将消息链按职责范围进行排序。ChainofResponsibility设计模式的缺点是不能保证一个消息被可靠地处理。有时,一个消息传遍整个消息链,也没有找到一个有效的处理方法,这样就会造成程序的“黑洞”。另外,如果消息链的顺序没有被很好地组织,也会发生消息不被处理的情况。
本章小结
本章开始部分介绍了有关设计模式的定义、设计模式的功能作用、有关设计模式的描述方法以及设计模式的使用等相关方面的问题,特别强调了服装管理软件开发人员要注意避免在使用设计模式时的思维误区。后面主要介绍了GOF设计模式的23种分类,以及各类设计模式的描述。最后分析了设计模式在绘画图形时的实际运用,帮助读者快速体会到使用设计模式的好处。
文章来源:秘奥软件网,中小企业信息化领跑者!全国咨询热线:400-9908-527_www.misall.com
Copyright @ 2007 MISALL Corporation. All Rights Reserved. All Powered By 粤ICP备07050206号
地址:广州天河区大观南路26号长盛商务大厦B713、715 电话:020-28269517