1、软件设计模式,黄洪 13605804361,学习面向对象设计中前人最有价值的经验总结-设计模式。以便重用优秀、简单的、经过验证的问题解决方案。 设计模式实际上讨论的是在解决面向对象设计的某类问题时,应该设计那些类,这些类之间应该如何通信。 设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。设计模式帮助你做出有利于系统复用的选择,避免设计损害了系统复用性。通过提供一个显式类和对象作用关系以及它们之间潜在联系的说明规范,设计模式甚至能够提高已有系统的文档管理和系统维护的有效性。简而言之,设计模式可以帮助设计者更快更好地完
2、成系统设计。 学习设计模式的重要性,课程目的,参考书目,(美) GoF, 设计模式-可服用的面向对象软件的基础,机械工业出版社,2005 (美)Elisabeth Freeman,Eric Freeman,Bert Bates,Kathy Sierra ,深入浅出设计模式(英文影印版),东南大学出版社,2005 (美)Alan Shalloway,James R.Trott,设计模式精解,清华大学出版社,2004 洁城 浩,设计模式-JAVA语言中的应用,中国铁道出版社2005.1 http:/ 引言,主要内容什么是设计模式 设计模式的描述 设计模是怎样解决设计问题 怎样选择设计模式 怎样使用
3、设计模式,什么是设计模式,著名建筑师Christopher Alexander 说过:“每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样,你就能一次又一次地使用该方案而不必做重复劳动” 他的思想也同样适用于面向对象设计模式,只是在面向对象的解决方案里,我们用对象和接口代替了墙壁和门窗。两类模式的核心都在于提供了相关问题的解决方案。 设计模式并不是一种具体“技术“,它讲述的是思想,它不仅仅展示了接口或抽象类在实际案例中的灵活应用和智慧,让你能够真正掌握接口或抽象类的应用,从而 在原来的Java语言基础上跃进一步,更重要的是,GoF的设计模式反复向你强调一个宗旨:
4、要让你的程序尽可能的可重用。,设计模式,内行的设计者知道:不是解决任何问题都要从头做起。他们更愿意复用以前使用过的解决方案。当找到一个好的解决方案,他们会一遍又一遍地使用。这些经验是他们成为内行的部分原因。因此,你会在许多面向对象系统中看到类和相互通信的对象( c o m m u n i c a t i n go b j e c t)的重复模式。这些模式解决特定的设计问题,使面向对象设计更灵活、优雅,最终复用性更好。它们帮助设计者将新的设计建立在以往工作的基础上,复用以往成功的设计方案。 一个熟悉这些模式的设计者不需要再去发现它们,而能够立即将它们应用于设计问题中。,设计模式,设计模式是人们对
5、成功的,可供重复使用的设计经验的总结。 学习设计模式使人们可以更加简单方便地复用成功的设计和体系结构。将已证实的技术表述成设计模式也会使新系统开发者更加容易理解其设计思路。设计模式帮助你做出有利于系统复用的选择,避免设计损害了系统复用性。通过提供一个显式类和对象作用关系以及它们之间潜在联系的说明规范,设计模式甚至能够提高已有系统的文档管理和系统维护的有效性。简而言之,设计模式可以帮助设计者更快更好地完成系统设计。,设计模式的定义,一个设计模式命名、抽象和确定了一个通用设计结构的主要方面,这些设计结构能被用来构造可复用的面向对象设计。设计模式确定了所包含的类和实例,它们的角色、协作方式以及职责分
6、配。每一个设计模式都集中于一个特定的面向对象设计问题或设计要点,描述了什么时候使用它,在另一些设计约束条件下是否还能使用,以及使用的效果和如何取舍。,模式的四个要素,模式名称(pattern name)一个助记名,它用一两个词来描述模式的问题、解决方案和效果。 问题(problem) 描述了应该在何时使用模式。它解释了设计问题和问题存在的前因后果,它可能描述了特定的设计问题,如怎样用对象表示算法等。也可能描述了导致不灵活设计的类或对象结构。有时候,问题部分会包括使用模式必须满足的一系列先决条件。 解决方案(solution) 描述了设计的组成成分,它们之间的相互关系及各自的职责和协作方式。因为
7、模式就像一个模板,可应用于多种不同场合,所以解决方案并不描述一个特定而具体的设计或实现,而是提供设计问题的抽象描述和怎样用一个具有一般意义的元素组合(类或对象组合)来解决这个问题。 效果(consequences) 描述了模式应用的效果及使用模式应权衡的问题。,描述设计模式,用统一的格式描述设计模式,对一个模式的描述包括以下部分: 模式名和分类 意图:设计模式是做什么的?它的基本原理和意图是什么?它解决的是什么样的特定设计问题? 动机:用以说明一个设计问题以及如何用模式中的类、对象来解决该问题的特定情景。该情景会帮助你理解随后对模式更抽象的描述。 适用性:什么情况下可以使用该设计模式?该模式可
8、用来改进哪些不良设计?你怎样识别这些情况? 结构:采用基于对象建模技术对模式中的类进行图形描述。使用了交互图来说明对象之间的请求序列和协作关系。,描述设计模式(续),参与者:指设计模式中的类和/或对象以及它们各自的职责。 协作:模式的参与者怎样协作以实现它们的职责。 效果:模式怎样支持它的目标?使用模式的效果和所需做的权衡取舍?系统结构的哪些方面可以独立改变? 实现:实现模式时需要知道的一些提示、技术要点及应避免的缺陷,以及是否存在某些特定于实现语言的问题。 代码示例:用来说明怎样用Java实现该模式的代码片段。 已知应用:实际系统中发现的模式的例子。 相关模式:与这个模式紧密相关的模式有哪些
9、?其间重要的不同之处是什么?这个模式应与哪些其他模式一起使用?,设计模式的编目,Abstract Factory( 3 . 1 ):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 A d a p t er ( 4 . 1 ):将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 B r i d g e( 4 . 2 ):将抽象部分与它的实现部分分离,使它们都可以独立地变化。 B u i l d e r( 3 . 2 ):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
10、 Chain of Responsibility( 5 . 1 ):为解除请求的发送者和接收者之间耦合,而使多个对象都 有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。,GoF(Gang of Four),Erich Gamma Richard Helm Ralph Johnson John Vlissides 1994合著 Design Patterns Elements of Reusable Object-Oriented Software,设计模式的编目,C o m m a n d( 5 . 2 ):将一个请求封装为一个对象,从而使你可用不同的请
11、求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。 C o m p o s i t e( 4 . 3 ):将对象组合成树形结构以表示“部分-整体”的层次结构。C o m p o s i t e使得客户对单个对象和复合对象的使用具有一致性。 D e c o r a t o r( 4 . 4 ):动态地给一个对象添加一些额外的职责。就扩展功能而言, D e c o r a t o r模式比生成子类方式更为灵活。 F a c a d e( 4 . 5 ):为子系统中的一组接口提供一个一致的界面, F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。,设
12、计模式的编目,Factory Method( 3 . 3 ):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。 F l y w e i g h t( 4 . 6 ):运用共享技术有效地支持大量细粒度的对象。 I n t e r p r e t e r( 5 . 3 ):给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。 I t e r a t o r( 5 . 4 ):提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。,设计模式的编目,M e d i a
13、t o r( 5 . 5 ):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。 M e m e n t o( 5 . 6 ):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。 O b s e r v e r( 5 . 7 ):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。 P r o t o t y p e( 3 . 4 ):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。,
14、设计模式的编目,P r o x y( 4 . 7 ):为其他对象提供一个代理以控制对这个对象的访问。 S i n g l e t o n( 3 . 5 ):保证一个类仅有一个实例,并提供一个访问它的全局访问点。 S t a t e( 5 . 8 ):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。 S t r a t e g y (5 . 9 ):定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。 Template Method( 5 . 1 0 ):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Tem
15、plate Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。 Vi s i t o r( 5 . 11 ):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。,设计模式分类,根据模式目的来分。即模式是用来完成什么工作的。模式依据其目的可分为创建型( C r e a t i o n a l )、结构型( S t r u c t u r a l )、或行为型( B e h a v i o r a l )三种。 创建型模式与对象的创建有关; 结构型模式处理类或对象的组合; 行为型模式对类或对象怎样交互和怎样分配职责进
16、行描述。 根据模式的范围分,指定模式主要是用于类还是用于对象。 类模式处理类和子类之间的关系,这些关系通过继承建立,是静态的,在编译时刻便确定下来了。 对象模式处理对象间的关系,这些关系在运行时刻是可以变化的,更具动态性。 从某种意义上来说,几乎所有模式都使用继承机制,所以“类模式”只指那些集中于处理类间关系的模式,而大部分模式都属于对象模式的范畴。,模式分类表,模式之间的关系,还有一种方式是根据模式的“相关模式”部分所描述的它们怎样互相引用来组织设计模式。 图1 - 1给出了模式关系的图形说明。,设计模式怎样解决设计问题,设计模式采用多种方法解决面向对象设计者经常碰到的问题。这里给出几个问题
17、以及使用设计模式解决它们的方法。 寻找合适的对象 决定对象的粒度 指定对象接口 描述对象的实现 运用复用机制 关联运行时刻和编译时刻的结构 设计应支持变化,设计模式帮助寻找合适的对象,面向对象设计最困难的部分是将系统分解成对象集合。因为要考虑许多因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等等,它们都影响着系统的分解,并且这些因素通常还是互相冲突的。 设计的许多对象来源于现实世界的分析模型。但是,设计结果所得到的类通常在现实世界中并不存在。严格反映当前现实世界的模型并不能产生也能反映将来世界的系统。设计中的抽象对于产生灵活的设计是至关重要的。 设计模式可以帮助确定并不明显的抽象和描述这
18、些抽象的对象。例如,描述过程或算法的对象现实中并不存在,但它们却是设计的关键部分。S t r a t e g y 模式描述了怎样实现可互换的算法族。S t a t e 模式将实体的每一个状态描述为一个对象。这些对象在分析阶段,甚至在设计阶段的早期都并不存在,后来为使设计更灵活、复用性更好才将它们发掘出来。,设计模式帮助决定对象的粒度,对象在大小和数目上变化极大。它们能表示下自硬件或上自整个应用的任何事物。那么我们怎样决定一个对象应该是什么呢? 设计模式很好地讲述了这个问题。F a c a d e 模式描述了怎样用对象表示完整的子系统,F l y w e i g h t 模式描述了如何支持大量的
19、最小粒度的对象。其他一些设计模式描述了将一个对象分解成许多小对象的特定方法。Abstract Factory和B u i l d e r 产生那些专门负责生成其他对象的对象。Vi s i t o r 和C o m m a n d 生成的对象专门负责实现对其他对象或对象组的请求。,对象接口,一个操作对应的操作名、作为参数的对象和返回值,这就是所谓的操作的型构( s i g n a t u r e )。一个对象所定义的所有操作型构的集合被称为该对象的接口( i n t e r f a c e )。在面向对象系统中,接口是基本的组成部分。对象只有通过它们的接口才能与外部交流,如果不通过对象的接口就无
20、法知道对象的任何事情,也无法请求对象做任何事情。对象接口与其功能实现是分离的,不同对象可以对请求做不同的实现,也就是说,两个有相同接口的对象可以有完全不同的实现。,对象接口(动态绑定),当给对象发送请求时,所引起的具体操作既与请求本身有关又与接受对象有关。支持相同请求的不同对象可能对请求激发的操作有不同的实现。发送给对象的请求和它的相应操作在运行时刻的连接就称之为动态绑定(dynamic binding)。,对象接口(多态),动态绑定是指发送的请求直到运行时刻才受你的具体的实现的约束。因而,在知道任何有正确接口的对象都将接受此请求时,你可以写一个一般的程序,它期待着那些具有该特定接口的对象。进
21、一步讲,动态绑定允许你在运行时刻彼此替换有相同接口的对象。这种可替换性就称为多态( p o l y m o r p h i s m ),它是面向对象系统中的核心概念之一。多态允许客户对象仅要求其他对象支持特定接口,除此之外对其假设几近于无。多态简化了客户的定义,使得对象间彼此独立,并可以在运行时刻动态改变它们相互的关系。,设计模式帮助指定对象接口,设计模式通过确定接口的主要组成成分及经接口发送的数据类型,来帮助你定义接口。 设计模式也许还会告诉你接口中不应包括哪些东西。M e m e n t o ( 5 . 6 )模式是一个很好的例子,它描述了怎样封装和保存对象内部的状态,以便一段时间后对象能
22、恢复到这一状态。它规定了M e m e n t o对象必须定义两个接口:一个允许客户保持和复制m e m e n t o的限制接口,和一个只有原对象才能使用的用来储存和提取m e m e n t o中状态的特权接口。 设计模式也指定了接口之间的关系。特别地,它们经常要求一些类具有相似的接口;或它们对一些类的接口做了限制。例如, D e c o r a t o r ( 4 . 4 )和P r o x y ( 4 . 7 )模式要求D e c o r a t o r和P r o x y对象的接口与被修饰的对象和受委托的对象一致。而Vi s i t o r ( 5 . 11 )模式中,Vi s i
23、t o r接口必须反映出v i s i t o r能访问的对象的所有类。,抽象类,抽象类(abstract class)的主要目的是为它的子类定义公共接口。一个抽象类将把它的部分或全部操作的实现延迟到子类中,因此,一个抽象类不能被实例化。在抽象类中定义却没有实现的操作被称为抽象操作(abstract operation)。非抽象类称为具体类(concrete class)。 子类能够改进和重新定义它们父类的操作。更具体地说,类能够重定义( o v e r r i d e )父类定义的操作,重定义使得子类能接管父类对请求的处理操作。类继承允许你只需简单的扩展其他类就可以定义新类,从而可以很容易地
24、定义具有相近功能的对象族。,类继承与接口继承的比较,理解对象的类( c l a s s )与对象的类型( t y p e -Java中的接口Interface)之间的差别非常重要。 一个对象的类定义了对象是怎样实现的,同时也定义了对象的内部状态和操作的实现。但是对象的类型只与它的接口有关,接口即对象能响应的请求的集合。一个对象可以有多个类型,不同类的对象可以有相同的类型。 当然,对象的类和类型是有紧密关系的。因为类定义了对象所能执行的操作,也定义了对象的类型。当我们说一个对象是一个类的实例时,即指该对象支持类所定义的接口。,设计模式帮助描述对象的实现,面向对象设计的第一个原则:针对接口编程,而
25、不是针对实现编程。 不将变量声明为某个特定的具体类的实例对象,而是让它遵从抽象类所定义的接口。当你不得不在系统的某个地方实例化具体的类(即指定一个特定的实现)时,创建型模式(Abstract Factory(3.1),B u i l d e r ( 3 . 2 ),Factory Method(3.3),P r o t o t y p e ( 3 . 4 )和S i n g l e t o n ( 3 . 5 ) )可以帮你。通过抽象对象的创建过程,这些模式提供不同方式以在实例化时建立接口和实现的透明连接。创建型模式确保你的系统是采用针对接口的方式书写的,而不是针对实现而书写的。,运用复用机制
26、,对象组合是类继承之外的另一种复用选择。新的更复杂的功能可以通过组装或组合对象来获得。对象组合要求被组合的对象具有良好定义的接口。这种复用风格被称为黑箱复用(black-box reuse),因为对象的内部细节是不可见的。,继承机制的优缺点,优点: 类继承是在编译时刻静态定义的,且可直接使用,因为程序设计语言直接支持类继承。类继承可以较方便地改变被复用的实现。当一个子类重定义一些而不是全部操作时,它也能影响它所继承的操作,只要在这些操作中调用了被重定义的操作。 缺点 因为继承在编译时刻就定义了,所以无法在运行时改变从父类继承的实现。 继承常被认为“破坏了封装性” 。子类中的实现与它的父类有如此
27、紧密的依赖关系,以至于父类实现中的任何变化必然会导致子类发生变化。 如果继承下来的实现不适合解决新的问题,则父类必须重写或被其他更适合的类替换。这种依赖关系限制了灵活性并最终限制了复用性。 一个可用的解决方法就是只继承抽象类,因为抽象类通常提供较少的实现。,对象组合的优点,对象组合是通过获得对其他对象的引用而在运行时刻动态定义的。组合要求对象遵守彼此的接口约定,进而要求更仔细地定义接口,而这些接口并不妨碍你将一个对象和其他对象一起使用。这还会产生良好的结果:因为对象只能通过接口访问,所以我们并不破坏封装性;只要类型一致,运行时刻还可以用一个对象来替代另一个对象;更进一步,因为对象的实现是基于接
28、口写的,所以实现上存在较少的依赖关系。 对象组合对系统设计还有另一个作用,即优先使用对象组合有助于你保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。另一方面,基于对象组合的设计会有更多的对象(而有较少的类),且系统的行为将依赖于对象间的关系而不是被定义在某个类中。,设计模式倡导对象组合的复用方式,面向对象设计的第二个原则:优先使用对象组合,而不是类继承。 理想情况下,你不应为获得复用而去创建新的构件。你应该能够只使用对象组合技术,通过组装已有的构件就能获得你需要的功能。但是事实很少如此,因为可用构件的集合实际上并不足够丰富。使用继
29、承的复用使得创建新的构件要比组装旧的构件来得容易。这样,继承和对象组合常一起使用。 经验表明:设计者往往过度使用了继承这种复用技术。但依赖于对象组合技术的设计却有更好的复用性(或更简单)。设计模式中一再使用对象组合技术。,怎样选择设计模式,使用设计模式的最佳途径将各种设计模式烂熟于心,在你进行设计时识别在哪些地方能够运用这些设计模式。对于初学者来说,要找出一个针对特定设计问题的模式可能还是很困难的,尤其是当面对一组你还不怎么熟悉的新模式时。下面给出几个不同的方法,帮助你发现适合你手头问题的设计模式: 考虑设计模式是怎样解决设计问题的 前面讨论了设计模式怎样帮助你找到合适的对象、决定对象的粒度、
30、指定对象接口以及设计模式解决设计问题的几个其他方法。参考这些讨论会有助于你找到合适的模式。 浏览模式的意图部分 通读每个模式的意图,找出和你的问题相关的一个或多个模式。你可以使用模式分类方法缩小你的搜查范围。 研究模式怎样互相关联 图1-1 以图形方式显示了设计模式之间的关系。研究这些关系能指导你获得合适的模式或模式组。 研究目的相似的模式 洞察具有相似目的的模式之间的共同点和不同点。,怎样选择设计模式(续),检查重新设计的原因 看一看从“设计应支持变化”小节开始讨论的引起重新设计的各种原因,再看看你的问题是否与它们有关,然后再找出哪些模式可以帮助你避免这些会导致重新设计的因素。 考虑你的设计
31、中哪些是可变的 这个方法与关注引起重新设计的原因刚好相反。它不是考虑什么会迫使你的设计改变,而是考虑你想要什么变化却又不会引起重新设计。最主要的一点是封装变化的概念,这是许多设计模式的主题。,怎样使用设计模式,有效应用设计模式的循序渐进的方法。 1) 大致浏览一遍模式特别注意其适用性部分和效果部分,确定它适合你的问题。 2 ) 回头研究结构部分、参与者部分和协作部分确保你理解这个模式的类和对象以及它们是怎样关联的。 3 ) 看代码示例部分,看看这个模式代码形式的具体例子研究代码将有助于你实现模式。 4 ) 选择模式参与者的名字,使它们在应用上下文中有意义设计模式参与者的名字通常过于抽象而不会直
32、接出现在应用中。然而,将参与者的名字和应用中出现的名字合并起来是很有用的。这会帮助你在实现中更显式的体现出模式来。,怎样使用设计模式(续),5) 定义类声明它们的接口,建立它们的继承关系,定义代表数据和对象引用的实例变量。识别模式会影响到的你的应用中存在的类,做出相应的修改。 6) 定义模式中专用于应用的操作名称这里再一次体现出,名字一般依赖于应用。使用与每一个操作相关联的责任和协作作为指导。还有,你的名字约定要一致。例如,可以使用“C r e a t e-”前缀统一标记F a c t o r y方法。 7) 实现执行模式中责任和协作的操作实现部分提供线索指导你进行实现。代码示例部分的例子也能提供帮助。 这些只对你一开始使用模式起指导作用。以后你会有自己的设计模式使用方法。,怎样使用设计模式(续),选择设计模式时,应该注意它们的使用限制。设计模式不能够随意使用。设计模式通常在通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更复杂并/或牺牲了一定的性能。因此,只有当一个设计模式提供的灵活性是真正需要的时候,才有必要使用。 选择设计模式时,衡量它的效果究竟如何使重要的。这可以通过查看模式说明的效果部分,来获得帮助。,