设计模式:可复用面向对象软件的基础 分析阅读
Posted castamere
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式:可复用面向对象软件的基础 分析阅读相关的知识,希望对你有一定的参考价值。
设计模式代码参考
一句话描述设计模式(名字和意图)
- 抽象工厂:提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
- 适配器:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- 桥接:将抽象部分与他的实现部分分离,使他们都可以独立地变化。
- 建造者:将一个复杂对象的构建与他的表示分离,使得同样的构建过程可以创建不同的表示。
- 责任链:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
- 命令:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
- 组合:将对象组合成树形结构以表示“部分-整体”的层次结构。组合使客户对单个对象和复合对象的使用具有一致性。
- 装饰器:动态地给一个对象添加一些额外的职责。就扩展功能而言,装饰器模式比生成子类方式更加灵活。
- 外观:为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
- 工厂方法:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法使一个类的实例化延迟到其子类。
- 享元:运用共享技术有效地支持大量细粒度的对象。
- 解释器:给定一个语言,定义他的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
- 迭代器:提供一种方法顺序访问一个聚合对象中各个元素,而又无需暴露该对象的内部表示。
- 中介者:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式的相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
- 备忘录:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到保存的状态。
- 观察者:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并自动刷新。
- 原型:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
- 代理:为其他对象提供一个代理以控制对这个对象的访问。
- 单例:保证一个类只有一个实例,并提供一个访问它的全局访问点。
- 状态:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
- 策略:定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。本模式使得算法的变化可独立于使用它的客户。
- 模板方法:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
- 访问者:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
一些导致重新设计的一般原因,以及解决这些问题的设计模式
- 通过显示地指定一个类来创建对象 。
创建对象时指定类名将使我们受实现的约束,应该面向接口编程,这种时候应该间接地创建对象。
解决模式:抽象工厂、工厂方法、原型
- 对特殊操作的依赖。
当我们为请求指定一个特殊的操作时(炸酱面多加葱蒜不要香菜,特麻特辣),完成该请求的方式就固定下来了,这不利于代码复用,且代码死板。为避免吧请求代码写死,可以在编译或者运行时刻很方便地改变相应请求的方法。
解决模式:责任链、命令
- 对硬件和软件平台的依赖。(待了解)
外部的操作系统接口和应用编程接口(API)在不同的软硬件平台上是不同的。依赖于特定平台的软件将很难移植到其他平台上,这时候就需要对变化作出封装,设计系统时限制其平台相关性。
解决模式:抽象工厂、桥接
- 对对象表示或实现的依赖。
知道对象怎样表示、保存、定位或实现的客户在对象发生变化时可能也需要变化,对客户隐藏这些信息可以阻止连锁变化。最小知道原则。
解决模式:抽象工厂、桥接、备忘录、代理
- 算法依赖
算法在开发和复用时常常被扩展、优化和替代。依赖于某个特定算法的对象在算法发生变化时不得不变化。因此对于可能发生变化的算法应该被孤立起来。
解决模式:建造者、迭代器、策略、模板方法、访问者
- 紧耦合
紧耦合的类很难独立地被复用,因为强依赖关系。要改变或删掉一个紧耦合的类,需要理解和改变其他许多类,这样的系统难以学习、移植和维护。
松散耦合提高了一个类本身被复用的可能性,设计模式使用抽象耦合和分层技术来提高系统的松散耦合性。
解决模式:抽象工厂、命令、外观、中介者、观察者、责任链
- 通过生成子类来扩充功能。
通过生成子类来扩充功能会导致类爆炸;通常很难通过定义子类来定制对象,定义子类需要对父类有深入的了解(里式替换)。一般通过对象组合技术和具体的委托技术,这是继承职位组合对象的另一种灵活方法。
小细节待实践发现:过多使用对象组合会使设计难于理解。可以定义一个子类,且将它的实例和已存在实例进行组合来引入定制的功能。
解决模式:桥接、责任链、组合、装饰器、观察者、策略
- 不能方便地对类进行修改
当不得不改变一个难以修改的类时,如源代码类,或者该类的改变会改变许多已存在的其他子类。
解决模式:适配器、装饰器、访问者
其他:
英语技术官网及其文档、英语技术书的学习和使用、查看浏览关键点技巧等需要去下功夫学习一下,可以去知乎上看看别人的建议。
主要是两个障碍,一个的技术单词含义不懂的障碍,还有就是新知识不理解,这两个一叠加我就根本不想看了。所以一方面慢慢积累技术单词量,理解一般计算用语中单词的含义;另一方面就是加强基础,加强技术的理解和基础知识水平,方便理解新知识。
小说家和剧作家很少从头开始设计剧情,他们总是沿袭一些已经存在的模式。如悲剧性英雄模式——麦克白、哈姆雷特。
每一个模式都描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。——Alexander
城市和建造模式。
待了解
- 分布式系统和并发程序的设计模式?
第一章属于理论部分,得回过头来重新看看。
设计模式的理解将使我们拥有非同寻常的思考面向对象设计能力,帮助我们设计出更加灵活、模块化、可复用、易理解的软件。
一个模式有4个基本要素:
- 模式名称 。 描述了模式的问题、解决方案和效果。
- 问题 。 描述了在何时使用模式。
- 解决方案 。描述了设计的组成成分,他们之间的相互关系及各自的职责和协作方式。
- 效果 。描述了模式应用的效果及使用模式应权衡的问题。
设计模式用来在特定场景下解决一般设计问题的类和相互通信的对象的描述。
模式分类
模式依据目的可以分为创建型、结构型、行为型三种。创建型模式与对象的创建有关;结构型模式处理类或对象的组合;行为型模式对类或对象怎样交互和怎样分辨职责进行描述。
模式依据范围准则指定模式主要用于类还是用于对象。
面向对象设计最困难的部分是将系统分解成对象集合。需要考虑的因素:封装、粒度、依赖关系、灵活性、性能、演化、复用等。
设计中的抽象对于产生灵活的设计是至关重要的。
针对接口编程,而不是针对实现编程。
当我们不得不在系统的某个地方实例化具体的类时,用创建型模式(6种)来实例化可以确保系统是裁员针对接口的方式编程的。
面向对象系统中功能复用的最常见两种技术是类继承和对象组合。
优先使用对象组合,少用类继承。
因为继承对子类揭示了父类的实现细节,所以继承常被认为破坏了封装性,且父类实现中的任何变化必然会导致子类发生变化。继承如果不适用则在重写后会导致继承关系限制了灵活性并最终限制了复用性。比较好的解决方法是只继承抽象类,因为抽象类通常提供较少的实现。
怎么选择设计模式
- 考虑设计模式是怎样解决设计问题的
- 浏览模式的意图部分
- 研究模式怎样互相关联
- 研究目的相似的模式
- 检查重新设计的原因
- 考虑设计中哪些是可变的。这与关注引起重新设计的原因刚好相反,这类要考虑的是我们想要什么变化却又不好引起重新设计,是封装变化的概念,下来列出设计模式允许我们使用时可以独立了变化的方面,改变它们不会导致重新设计。
怎样使用设计模式
- 大致浏览一遍模式。注意适用性和效果部分,确定它适合你的问题。
- 研究结构、参与者和协作部分。确保能理解这个模式的类和对象以及他们是怎么样关联的。
- 看代码示例。
- 选择模式参与者的名字,使他们在应用上下文中有意义。即使用了什么模式的类就用模式名命名该类。(###Factory、###Strategy)
- 定义类。申明接口,建立继承关系。
- 定义模式中专用于应用的操作名称。
- 实现执行模式中责任和协作的操作。
总结
设计模式的使用限制要特别注意:设计模式不能够随意使用。
通常在通过引入额外的间接层次获得灵活性和可变性的同时,也使设计变得更负责并/或牺牲了一定的性能。
一个设计模式只有当它提供的灵活性是真正需要的时候,才有必要使用。
衡量一个模式的得失时,通过他的效果部分是最能提供帮助的。
UML学习
抽象类的主要目的是为它的子类定义公共接口,非抽象类称为具体类。
第二章实践需重新看一遍
创建型模式
创建型模式抽象了实例化过程。
创建迷宫
抽象工厂
意图:
提供一个创建一系列相关或相互依赖对象的接口,而无需指定他们具体的类。
适用性:
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而只想显示他们的接口而不是实现时。
效果
优点:
- 它分离了具体的类
- 它使得系统易于交换产品系列
- 它有利于产品的一致性
缺点: - 难以支持新种类的产品
结构:
相关模式:
抽象工厂类通常用工厂方法实现,也可以用原型实现。
一个具体的工厂通常是一个单例。
建造者
意图:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
适用性:
- 当创建复杂对象的算法应该独立于该对象的组成部分以及它们的装配方式时。
- 当构造过程必须允许被构造的对象有不同的表示时。
效果
优点:
- 它使你可以改变一个产品的内部表示
- 它将构造代码和表示代码分开
- 它使你可对构造过程进行更精细的控制
结构:
相关模式:
抽象工厂和建造者相似,都可以创建复杂的对象。区别是创建者模式着重于一步步构造一个复杂对象,而抽象工厂着重于多个系列的产品对象。建造者在最后一步返回产品,而抽象工厂的产品是立即返回的。
组合模式通常是用建造者模式生成的。(疑问?)
工厂方法
意图:
定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法使一个类实例化延迟到其子类。
适用性:
- 当一个类不知道它所必须创建的对象的类的时候。
- 当一个类希望由它的子类来指定它所创建的对象的时候。
- 当类将创建对象的职责委托给多个帮助子类中的某一个(疑问?),并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。
效果
优点:
- 为子类提供挂钩?
- 连接平行的类层次?
结构:
相关模式:
抽象工厂经常用工厂方法实现。
工厂方法通常在模板方法中被调用。
工厂方法与原型的区别:原型不需要创建工厂Creator的子类,但原型通常要求一个针对Product类的初始化操作。原型Creator使用Initialize来初始化对象,而工厂方法不需要这样的操作。
原型(最简单的就是要被复杂创建的类加个Clone方法)
意图:
用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。
适用性:
先前提:当一个系统应该独立于它的产品创建、构成和表示时,使用原型模式;且满足以下条件之一:
- 当要实例化的类是在运行时刻指定时,如通过动态装载;
- 为避免创建一个与产品类层次平行的工厂类层次时;?
- 当一个类的实例只能有几个不同状态组合的一种时。建立相应数目的原型并克隆他们可能比每次用合适的状态手工实例化该类更方便一些。
效果
优点:
- 运行时刻增加和删除产品(疑问?)
- 改变值以指定新对象(疑问?)
- 改变结构以指定新对象?
- 减少子类的构造
- 用类动态配置应用
浅拷贝和深拷贝问题:
结构:
相关模式:
//原型的使用 org.springframework.beans.BeanUtils
BeanUtils.copyProperties(repayOverview,re);
单例
意图:
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
适用性:
- 当类只能有一个实例而且客户可以从一个总所周知的访问点访问它时。
- 当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。 (疑问?)
效果
优点:
- 对唯一实例的受控访问
- 缩小名空间(对java来说这点没用)
- 允许对操作和表示的精化(没发现优点?)
- 允许可变数目的实例(单例就只有一个,多个实例是哪边有用到,又如何叫单例?)
- 比类操作更灵活(疑问?)
结构:
相关模式:
很多。
创建型模式讨论
一般一开始会使用工厂方法模式,如果需要更复杂或者对应的变化时再改用其他创建型模式。
结构型模式
结构型模式涉及到如何组合类和对象以获得更大的结构。结构型类模式采用继承机制来组合接口或实现。
适配器
意图:
将一个类的接口装换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适用性:
- 你想使用一个已经存在的类,而它的接口不符合你的需求。
- 你想创建一个可以复用的类,该类可以与其它不相关的类或不可预见的类(如接口不一定兼容的类)协同工作。
- 你想使用一些已经存在的子类,但是不可能对每一个都进行子类化以匹配它们的接口。对象适配器可以视频它的父类接口。(疑问?)
效果
对象适配器优点:
- 允许一个Adapter与多个Adaptee ——即被适配者本身及其所有子类一同工作。适配器也可以一次给所有的被适配者添加功能。
缺点: - 使得重定义被适配者的行为比较困难。这样需要生成被适配者子类并引用其子类。
结构:
相关模式:
桥接的结构与对象适配器类似,但桥接模式的出发点不同:桥接的目的是将揭开部分和实现部分分离,从而对它们较为容易也相对独立的加以改变,而适配器则是为了适配去改变一个已有对象的接口。
装饰器模式增强了装饰对象的功能且不改变装饰对象的接口。因此装饰器对应用程序的透明性比适配器要好。装饰器支持递归组合,而适配器不支持实现这一点。
代理模式在不改变他的接口的条件下,为另一个对象定义了一个代理,与适配器的意图不同。
桥接
意图:
将抽象部分与他的实现部分分离,使他们都可以独立得变化。
适用性:
- 你不希望在抽象和它的实现部分之间有一个固定的绑定关系。希望在程序运行时刻实现部分应可以被选中或者切换。
- 类的抽象以及它的实现都应该可以通过生成子类的方法加以扩充。使用桥接使你可以对不同的抽象接口和实现部分进行组合,并分别对它们进行扩充。
- 对一个抽象的实现部分的修改应对客户不产生影响,客户代码不必重新编译。
- 对客户完成因此抽象的实现部分。
- 使用时必须将一个对象分解成两个部分,及该对象可以有两种分类方法。
- 你想在多个对象间共享实现,但同时客户并不知道这一点。
效果
优点:
- 分离接口及其实现部分。
- 提供可扩充性,可以独立地对两个类层次进行扩充。
- 实现细节对客户透明。
结构:
相关模式:
抽象工厂模式可以用来创建和配置一个特定的桥接模式。
桥接与适配器的区别:适配器模式用来帮助需要适配的类协同工作,它通常在系统设计完成后不得已为了适配等才会被用到,而桥接则在系统开始时就被使用,它使得抽象接口和实现部分可以独立进行改变。
组合
意图:
将对象组合成树形结构以表示“部分--整体”的层次结构。组合使得用户对单个对象和组合对象的使用具有一致性。
组合模式描述了如何使用递归组合,使得用户不必对组合和个体的类进行区别对待。
视图组件、财经应用中的资产组合中经常使用。
适用性:
- 你想表示对象的部分-整体层次结构。(树形结构)
- 你希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所以对象。
效果
优点:
- 定义了包含基本对象和组合对象的类层次结构
- 简化客户代码
- 使得更容易增加新类型的组件(新的组合和也节点)
- 使你的设计变得更加一般化
结构:
相关模式:
通常部件-父部件连接用于责任链模式。(疑问?)
装饰器模式经常与组合模式一起使用。当装饰和组合一起使用时,他们通常有一个公共的父类。因此装饰必须支持具有Add / Remove / GetChild 操作的Component 接口。
享元让你共享组件,但不再能引用他们的父部件。(疑问?)
迭代器可以用来遍历组合。
访问者模式将本来应该分布在组合和叶节点的操作和行为局部化。(局部化什么意思?)
装饰器
意图:
动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
适用性:
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤销的职责。
- 当不能采用生成子类的方法进行扩充时。一直是有大量独立的扩展导致子类数目爆炸式增长;另一种是类定义被隐藏,或者类定义不能用于生成子类。
效果
优点:
- 比静态继承更灵活。为一个特定组件类提供多个装饰类,还可以使得你对一些职责进行混合和匹配。
- 避免在层次结构高层的类有太多的特征。(为组件类保持足够的简单,避免其复杂化而暴露与添加职责无关的细节)
缺点: - 装饰类和它修饰的组件类不一样
- 有许多小对象。采用装饰器模式设往往会产生许多看上去类似的小对象,不了解的人很难学习和排错。
使用注意
- 接口一致性。装饰器接口必须与它所装饰的组件接口一直,相同的父类或接口。
- 省略抽象的装饰类。如果只有简单一个职责则不需要定义抽象装饰类。
- 保持组件Component类的简单性。
- 改变对象外壳与改变对象内核。装饰器相当于改变对象的外壳,策略模式相当于改变对象的内核。当组件类原本就很庞大时使用装饰器代价太高,应改用策略模式。
结构:
相关模式:
装饰器与适配器的区别:装饰器增加对象的职责而不改变他的接口;适配器将给对象一个全新的接口。
策略和装饰器的区别:装饰器可以改变对象的外壳;而策略使你可以改变对象的内核,这是改变对象的两种途径。
外观
意图:
为子系统中的一组接口提供一个一致的界面,外观模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
适用性:
- 当你要为一个复杂子系统提供一个简单接口时。(如果需要更多的可定制性的用户可以越过facade层)
- 客户程序与抽象类的实现部分之间存在着很大的依赖性。引入facade将这个子系统与客户以及其他子系统分离,可以提供子系统的独立性和可移植性。
- 当你需要构建一个层次结构的子系统时,使用facade 模式定义子系统中每层的入口点。
效果
优点:
- 它对客户屏蔽子系统组件,因而减少了客户处理的对象的数目并使得子系统使用起来更加方便。
- 它实现了子系统与客户之间的松耦合关系,而子系统内部的功能组件往往是紧耦合的。
- 如果应用需要,它并不限制它们使用子系统类。因此你可以在系统易用性和通用性之间加以选择。
注意: - 用抽象类或接口实现facade来降低客户-子系统之间的耦合度。
结构:
相关模式:
抽象工厂模式可以与外观模式一起使用以提供一个接口,这一接口可用来以一种子系统独立的方式创建子系统对象。抽象工厂也可以代替外观模式隐藏那些与平台先关的类。
中介者与外观的相似处:他们都抽象了一些已有的类的功能。区别:中介者的目的是对同事之间的任意通讯进行抽象,通常集中不属于任何单个对象的功能。中介者的同事对象知道中介者并与它通信,而不是直接与其它同类对象通信。而外观模式仅对子系统对象的接口进行抽象,从而使他们更容易使用,它并不定义新功能,子系统也不知道外观的存在。
外观对象通常仅需要一个,一般用单例构造。
享元
意图:
运用共享技术有效地支持大量细粒度的对象。
例子:文档编辑器中字母的对象共享。
适用性:
- 一个应用程序使用了大量的对象。
- 完全是由于使用大量的对象,造成很大的存储开销时。
- 对象的大多数状态都可变为外部状态。
- 如果删除对象的外部状态,那么可以用相对较少的共享对象取代很多组对象。
- 应用程序不依赖对象标识。由于享元对象可以被共享,对概念上明显有别的对象,标识测试将返回真值。
注意:
- 享元执行是所需的状态必定是内部的和外部的。内部状态存储于ConcreteFlyweight 对象之中,而外部对象则由Client对象存储或计算。当用户调用享元对象的操作时,将该对象传递给它。(将外部状态作为参数传递给享元对象)
- 用户不应该直接对ConcreteFlyweight 类进行实例化,而只能从FlyweightFactory (工厂)对象中得到,这样可以抱着对它们适当地进行共享。
效果
优点:
- 共享的享元越多,空间节省就越大。
- 当对象使用大量的内部及外部状态,并且外部状态是计算出来的而非存储的时候,节约量将达到最大。因此用共享减少内部状态的消耗,用计算时间换取对外部状态的存储。
- 享元模式经常与组合模式结合起来表示一个层次式结构,该层次式结构是一个共享叶节点的图。(作用?)
结构:
相关模式:
享元模式经常与组合模式结合起来。
最好用享元实现状态模式和策略模式对象。
代理
意图:
为其他对象提供一种代理以控制对这个对象的访问。
适用性:
- 远程代理。为一个对象在不同的地址空间提供局部代表。
- 虚代理。根据需要创建开销很大的对象。
- 保护代理。控制对原始对象的访问。
- 智能指引。取代了简单的指针,他在访问对象时执行一些附加操作,如引用计数等。
效果
优点:
- 代理模式在访问对象时引入了一定程度的间接性。效果如上的适应性。
- 代理模式还可以对用户隐藏 copy-on-write的优化方式。
结构:
相关模式:
适配器与代理的区别:市北区是为它所适配的对象提供一个不同的接口;而代理提供的是与它的实体相同的接口。
装饰器与代理的区别:虽然两者实现结构相似,但两者目的不同。装饰器是为对象添加一个或多个功能,而代理则控制对对象的访问。更具体一点如远程代理并不包含对实体的直接引用,而是间接的,跟装饰器包含被装饰对象的引用不同,而虚拟代理则一开始并没有引用,而是在被调用时才创建真实的引用。
结构型模式讨论
适配器和桥接的区别:相同点:他们都给另一对象提供了一定程度上的间接性,因而有利于系统的灵活性。
不同点:它们的用途不一样。适配器注意解决两个已有接口之间不匹配的问题。桥接则对抽象接口与它的实现部分进行桥接。因此它们两常用于软件生命周期的不同阶段。适配器在开发后;桥接在设计类之前实施。它们两针对了不同的问题,没有好差之分。
外观与适配器的区别:外观定义了一个新的接口,而适配器则复用一个原来的接口,适配器使两个已有的接口协同工作,而不是定义一个全新的接口。
装饰和组合的区别:装饰旨在使你能够不需要生成子类即可给对象添加职责。而组合旨在构造类,使多个相关的对象能够以统一的方式处理,多重对象可以被当做一个对象来处理,它的重点不在于修饰,而在于表示。
装饰与代理的区别:两种都描述了怎样为对象提供一定程度上的间接引用。但目的不同,代理是用于当直接访问一个实体不方便或不符合需要时,为这个实体提供一个代替这,而装饰器是为了提供功能,装饰器适用于编译时不能(至少不方便)确定对象的全部功能的情况下使用。这种开放性使得递归组合成为装饰器模式中一个必不可少的部分;而代理模式不一样,他强调的是一种关系,即代理与他的实体之间的关系,这种关系可以静态的表达。
行为型模式
- 行为类模式使用继承机制在类见分派行为。包含模板方法和解释器两种。
- 行为对象模式使用对象复合而不是继承。
职责链
意图:
使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
例子:在线帮助系统;许多类库使用职责链模式处理用户事件。
适用性:
- 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
- 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可处理一个请求的对象集合应被动态指定。
效果
优点:
- 降低耦合度。职责链可简化对象的相互连接。他们仅需保持一个指向其后继者的引用,而不需保持他所有候选者的引用。
- 增强了给对象指派职责的灵活性
缺点: - 不保证被接受。
结构:
相关模式:
职责链常与组合一起使用。这种情况下,一个构件的父构件可作为它的后继。
命令
意图:
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。
适用性:
- 像菜单选项中的按钮对象一样,抽象出待执行的动作以参数化某对象。我们可以用过程语言中的回调函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。命令模式是回调机制的一个面向对象的替代品。
- 在不同的时刻指定、排列和执行请求。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么久可以将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
- 支持取消操作。命令的execute操作可在试试操作前将状态储存起来,在取消操作时这个状态用来消除该操作的影响。命令接口将必须添加一个unExecute操作,用来取消上一次execute操作的效果。执行的命令被存储在一个历史列表中。
- 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。Command 接口中添加装载操作和存储操作。当崩溃时可以磁盘中重新赌侠记录下来的明星并用execute命令重新执行它们。
- 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务的信息系统中很常见。
效果
优点:
- 命令模式将调研操作的对象与指导如何实现该操作的对象解耦。
- 命令对象是头等的对象。他可以像其他的对象一样被操纵和扩展。
- 我们可以将多个命令装配成一个复合命令。复合命令是组合模式的一个实例。
- 增加新的命令很容易,因为这无需改变已有的类。
结构:
相关模式:
组合模式可被用来实现宏命令。
备忘录模式可以用来保持某个状态,命令用这一状态来取消他的效果。如删除命令执行后的取消操作,需要复原被删除的对象信息。
解释器
意图:
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
适用性:
- 当有一个语言需要解释执行,并且你可以将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
- 该文法简单对于福州的文法,文法的类层次变得庞大而无法管理。
- 效率不是一个关键问题最搞笑的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们装换成另一种形式。例如正则表达式通常被转换为状态机。
效果
优点:
- 易于改变和扩展文法。可以通过继承来改变或扩展该文法。
- 易于实现文法。
- 增加了性的解释表达式的方式。使得实现新表达式“计算”变得容易。如果经常创建新的解释表达式的方式,那么可以考虑使用访问者模式以避免修改这些代表文法的类。
缺点: - 复杂的文法难以维护。当文法非常复杂时,其他的技术如语法分析程序或编译器生成器更为合适。
结构:
相关模式:
组合模式:抽象语法树是一个复合模式的实例。
享元模式:说明了如何在抽象语法树中共享终结符。
迭代器模式:解释器可用一个迭代器遍历该结构。
访问者模式:可用来在一个类中维护抽象语法树中的各节点的行为。
迭代器
意图:
提供一种方法顺序访问一个聚合对象中的各个元素,而又无需暴露该对象的内部表示。
适用性:
- 访问一个聚合对象的内容而无需暴露它的内部表示。
- 支持对聚合对象的多种遍历。
- 为遍历不同的聚合结构提供一个统一的接口,即多态迭代。
效果
优点:
- 它支持以不同的方式遍历一个聚合,复杂的聚合可用多种方式进行遍历。只需要一个不同的迭代器实例代替原型的实例即可。
- 迭代器简化了聚合的接口。
- 在同一个聚合上可以有多个遍历。每个迭代器保持它自己的遍历状态,因此可以同时进行多个遍历。
当客户来控制迭代时,该迭代器称为一个外部迭代器,而当由迭代器控制跌打时,该迭代器称为一个内部迭代器。外部迭代器比内部迭代器更灵活。如比较两集合是否相等时。
一个健壮的迭代器保证插入和删除操作不会干扰遍历,且不需要拷贝该聚合。做法是:大多数需要想这个聚合注册该迭代器。?当插入或删除元素时,该聚合要么调整迭代器的内部状态,要么在内部维护额外的信息以保证正确的遍历。
空迭代器(NullIterator)使得我们更容易遍历属性结构的聚合。当遇到聚合元素时将返回一个具体的迭代器,而叶节点元素返回NullIterator实例,使得我们可以用一种统一的方式实现在整个树形结构上的遍历。
结构:
相关模式:
组合模式:迭代器常被应用到像复合这样的递归结构上。
工厂方法:多态迭代器靠工厂方法来实例化适当的迭代器子类。
备忘录:常与迭代器一起使用。迭代器可使用一个备忘录来捕获一个迭代的状态。迭代器在其内部存储备忘录。
中介者
意图:
用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
适用性:
- 一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
- 一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
- 想定制一个分布在多个类中的行为,而又不行生成太多的子类。
效果
优点:
- 减少了子类生成。中介者将原本分布于多个对象间的行为集中在一起。改变这些行为只需要生成中介者子类即可。这样各个Colleague 类可被重用。
- 它将各Colleague 解耦。
- 它简化了对象协议。用一对多的交互代替多对多的交互。更易于理解、维护和扩展。
- 他对对象如何协作进行了抽象。将中介作为一个独立的概念并将其封装在一个对象中,使我们将注意力从对象各自本身的行为转移到他们之间的交互上来。有助于弄清楚一个系统中对象是如何交互的。
缺点: - 它使控制集中化。中介者模式将交互的复杂性变成中介者的复杂性。这可能使得中介者自身成为一个难以维护的庞然大物。
实现:
1.可以通过使用观察者模式,将中介者实现为一个观察者,各个Colleague 作为目标对象,一旦目标对象状态改变就发送通知给中介者,中介者做出相应将状态改变的结构传播给其他的Colleague。
2.另一个方法是在中介者中定义一个特殊的通知接口,各个Colleague在通信时直接调用该接口。
结构:
相关模式:
外观模式与中介者的不同之处在于它是对一个对象子系统进行抽象,从而提供了一个更为方便的接口。他的协议是单向的,即外观对象对这个子系统类提出请求,反之则不行。而中介者提供了各个Colleague对象不支持或不能支持的协作行为,而且协议是多向的。
Colleague 可使用观察者模式与中介者通信。
备忘录
意图:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
适用性:
- 必须保持一个对象在某一个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态。
- 如果一个用接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
效果
优点:
- 保持封装边界。使用备忘录可以避免暴露一些只应由原发器管理却又必须存储在原发器之外的信息。
- 假话了原发器。
缺点: - 使用备忘录可能代价很高。当生成备忘录时必须拷贝并存储大量信息时,频繁创建恢复备忘录会导致非常大的开销。
- 定义窄接口和宽接口。难以保证只有原发器可访问备忘录的状态。
- 维护备忘录的潜在代价。管理器不知道备忘录中有多少状态,可能会产生大量的存储开销。
结构:
相关模式:
命令模式可以用备忘录来为可撤销的操作维护状态。
备忘录可用于迭代。待理解
观察者
意图:
定义对象间的一种一对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。
适用性:
- 当一个抽象模型有两个方面,其中一个方面依赖于另一方面。将这两者封装在独立的对象中以使他们可以各自独立地改变和复用。
- 当对一个对象的改变需要同时改变其他对象,而不知道具体有多少对象有待改变。
- 当一个对象必须通知其他对象,而它又不能假定其他对象是谁。或者说,你不希望这些对象是紧密耦合的。
效果
优点:
- 目标和观察者间的抽象耦合。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它,这样就保持了系统层次的完整。
- 支持广播通信
缺点: - 意外的更新。如果依赖准备的定义或维护不当,常常会引起错误的更新。
使用时注意:
- 创建目标到其观察者之间的映射。一个目标对象跟踪它应通知的观察者的最简单的方法是显示地在目标中保存对它们的引用。但是当目标很多而观察者很少时,存储代价太高,可以改为用时间换空间的方法,即用一个管理查找机制,例如hash表来维护目标到观察者的映射。这样没有观察者的目标就不产生存储开销。但缺点是这方法增加了访问观察者的开销。
- 观察多个目标。如一个表格对象可能依赖于多个数据源。这是必须扩展update接口来使观察者知道是哪一目标送来的通知。如可以将目标对象自己作为参数传给update接口。
- 谁触发更新。有两种,一种是有目标对象的状态设定操作在改变目标对象的状态后自动调用Notify,优点是不需要客户端调用Notify,但如果有多个设置状态操作会产生多次更新,效率较低。第二种是让客户自己在适当的时候调用Notify,这样就避免了不必要的中间更新,但缺点是增加了客户端的触发更新责任,如果忘记容易出错。
- 避免特定于观察者的更新协议--推拉模型。a)目标想观察者发送关于改变的详细信息,而不管它们需要与否,这种为推模型。b)目标除最小通知外饰面也不送出,而在此之后由观察者显示地想目标询问细节,这种为拉模型。拉模型强调的是目标不知道他的观察者,而推模型假定目标制定一些观察者的需要的信息。推模型可能使得观察者相对难以复用,因为目标对观察者的假定可能并不总是正确的。(除非确定就是这种类型信息)》》》拉模型的缺点是效率较差,因为观察者需要在没有目标对象帮助的情况下确定是吗改变了。
结构:
相关模式:
中介者:通过封装复杂的更新语义,ChangeManager充当目标和观察者之间的中介者。
单例:ChangeManager可使用单例模式来保证它是唯一的并且是可全局访问的。
状态
意图:
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
例子:画图编辑器,通过使用状态模式来根据当前的工具改变编辑器的行为。
适用性:
- 一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
- 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。通常有多个操作包含这一相同的条件结构。状态模式将每一个条件分支放入一个独立的类中。这使得你可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化。
效果
优点:
- 它将特定状态相关的行为局部化,并且将不同状态的行为分割开来。
- 它使得状态转换显示化
- 状态对象可被共享
结构:
例子图表:
相关模式:
享元模式解释了何时以及怎样共享状态对象。
状态对象通常是单例的。
策略
意图:
定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。本模式使得算法可独立于使用它的客户而变化。
适用性:
- 许多相关的类仅仅是行为有异。策略提供了一种用多个行为中的一个行为来配置一个类的方法。
- 需要使用一个算法的不同变体。
- 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
- 一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的策略类中以代替这些条件语句。
效果
优点:
- 策略类层次为Context定义了一系列可供重用的算法或行为。
- 是一个替代继承的方法。将算法封装在独立的Strategy类中来代替继承更易于切换、理解和扩展。
- 消除了一些条件语句
- 提供不同实现的选择,策略模式可以提供相同行为的不同实现。
缺点: - 客户必须了解不同的策略
- 增加了对象的数目
结构:
相关模式:
享元:策略对象经常是很好的轻量级对象。
模板方法
意图:
定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
适用性:
- 一次性实现一个算法的不变的部分,并将可变的行为留给子类来实现。
- 各子类中公共的行为应被提取出来并集中到一个公共父类中以避免代码重复。
- 控制子类扩展。模板方法只在特定点调用钩子操作,这样就只允许在这些点进行扩展。
效果
优点:
- 模板方法是一种代码复用的基本技术。
- 模板方法是一种反向的控制结构,称为好莱坞法则。即别找我们,我们找你。指的是一个父类调用一个子类的操作,而不是相反。
钩子操作:它提供了缺省的行为,子类可以在必要时进行扩展,一个钩子操作在缺省操作中通常是一个空操作。
模板方法应该指明哪些操作是钩子操作(可以被重定义)以及哪些是抽象操作(必须被重定义)。
要有效地重用一个抽象类,子类编写者必须明确了解哪些操作是设计为有待重定义的。一般就是抽象方法。子类可以通过重定义父类的操作来扩展该操作的行为,扩展行为时还可显式地调用父类操作。
注意模板方法命名最好加个约定,如Do-
结构:
相关模式:
工厂方法:常被模板方法调用。
策略:模板方法使用继承来改变算法的一部分。策略使用委托来改变整个算法。
访问者
意图:
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
实现目标:可以独立地增加新的操作,并且是这些结点类(元素)独立于作用于其上的操作两个目标。
注意:使用访问者模式,必须定义两个类层次:一个对应于接收操作的元素(Node层次),另一个对应于定义对元素的操作的访问者(Node Visitor层次)。这样给访问者类层次增加一个新的子类就可创建一个新的操作。特别要注意的是Node层次是不变的,即不需要增加性的Node子类的前提下使用访问者。
适用性:
- 一个对象结构包含很多类对象,他们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
- 需要对一个对象结构中的对象镜像很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。
效果
优点:
- 访问者模式使得易于增加新的操作。
- 访问者集中相关的操作而分离无关的操作。
- 可以通过类层次进行访问。
缺点: - 增加新的ConcreteElement类很困难。
- 累积状态
- 破坏封装
双分派:
如果一个语言支持双分派或多分派,如CLOS,那么就不需要访问者模式了。
单分派语言通过访问者模式实现双分派技术。技术核心是通过我们的实现一个请求操作取决于两个方面:该请求的名和接收者的类型。双分派意味着得到执行的操作决定于请求的种类和两个接收者的类型。如Element中的Accept方法就是一个双分派操作。它的含义决定于两个类型:Visitor 的类型和Element的类型。双分派可以对每一类元素请求不同的操作。
访问者模式的关键在于:得到执行的操作不仅决定于访问者的类型还决定于它访问的元素的类型。
结构:
相关模式:
组合:访问者可以用于对一个由组合模式定义的对象结构进行操作。
解释器:访问者可以用于解释。
行为模式讨论
总结
设计模式是一套通用的设计词汇,方便技术人员交流沟通使用。
更好的理解现有的面向对象系统,因为里面用到了多种设计模式。
更好地运用设计模式重构。
建筑模式语言
Christopher Alexander :以一种松散的方式把一些模式串接在一起来建造建造是可能的。这样的建造仅仅是一些模式的对其,而不紧凑。这不够深刻。然而另外一种组合模式的方式,许多模式重叠在同一个物理空间里:这样的建造非常紧凑,在一小块空间里继承了许多内涵;由于这种紧凑,它变得深刻。
编程就是用最紧凑的代码,最极限的方式,最 大限度地实现功能。
其他知识补充:
多态时,编译看左边,运行看右边。看下面例子解释:
class Animal;
class Cat;
Animal a = new Cat();
//假设 Animal 和 Cat 都有 clone()方法 、name 实例变量;Cat 自己有miaomiao();方法
//则运行时 a.clone()调用的是Cat的方法(子类多态)而a.name 调用的是Animal的实例变量,因为实例变量不具备多态,如果想要拿到子类Cat的实例变量,则可以用强转方式把(Cat)a.name来获得子类的实例变量。
//如果调用a.miaomiao()会报错,因为Animal没有该方法,不能实现多态,会报编译失败。
以上是关于设计模式:可复用面向对象软件的基础 分析阅读的主要内容,如果未能解决你的问题,请参考以下文章