行为型模式是对在不同对象之间划分责任和算法的抽象化。
行为模式不仅仅关于类和对象,还关于它们之间的相互作用。
行为型模式又分为类的行为模式和对象的行为模式两种。
- 类的行为模式----使用继承关系在几个类之间分配行为。
- 对象的行为模式----使用对象聚合的方式来分配行为。
行为型模式包括11种模式: 模板方法模式、命令模式、迭代器模式、观察者模式、中介者模式、状态模式、策略模式、责任链模式、访问者模式、解释器模式、备忘录模式
- 模板方法模式:封装算法结构,定义算法骨架,支持算法子步骤变化。
- 命令模式:注重将请求封装为对象,支持请求的变化,通过将一组行为抽象为对象,实现行为请求者和行为实现者之间的解耦。
- 迭代器模式:注重封装特定领域变化,支持集合的变化,屏蔽集合对象内部复杂结构,提供客户程序对它的透明遍历。
- 观察者模式:注重封装对象通知,支持通信对象变化,实现对象状态改变,通知依赖它的对象并更新。
- 中介者模式:注重封装对象间的交互,通过封装一系列对象之间的复杂交互,使他们不需要显示相互引用,实现解耦。
- 状态模式:注重封装与状态相关的行为,支持状态的变化,通过封装对象状态,从而在其内部状态改变时改变它的行为。
- 策略模式:注重封装算法,支持算法的变化,通过封装一系列算法,从而可以随时独立于客户替换算法。
- 责任链模式:注重封装对象责任,支持责任的变化,通过动态构建职责连,实现事务处理。
- 访问者模式:注重封装对象操作变化,支持在运行时为类结构添加新的操作,在类层次结构中在不改变各类的前提下定义作用于这些类实现的新的操作。
- 备忘录模式:注重封装对象状态变化,支持状态保存,恢复
- 解释器模式:注重封装特定领域变化,支持领域问题的频繁变化,将特定领域的问题表达为某种语法规则下的句子,然后构建一个解释器来解释这样的句子,从而达到解决问题的目的。
模板方式模式
在一个抽象类中定义一个操作算法的骨架,将一些步骤延迟到子类中去实现。模板方法使得子类可以在不改变算法结构的情冴下,重新定义算法中的某些步骤。
通常我们会遇到这样的一个问题:我们知道一个算法所需的关键步聚,并确定了这些步聚的执行顺序。但是某些步聚的具体实现是未知的,或者是某些步聚的实现与具体的环境相关。
模板方法模式把我们不知道具体实现的步聚封装成抽象方法,提供一些按正确顺序调用它们的具体方法(这些具体方法统称为模板方法),这样构成一个抽象基类,子类通过继承这个抽象基类去实现各个步聚的抽象方法,而工作流程却由父类来控制。
定不变的封装到抽象类中,需要改变的在子类中去实现。
常用场景:一批子类的功能有可提取的公共算法骨架
选择关键点:算法骨架是否牢固
命令模式
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开
每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
迭代器模式
迭代器模式提供了一种方法顺序访问一个聚合对象(理解为集合对象)中各个元素,而又无需暴露该对象的内部表示,这样既可以做到不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。 迭代器模式为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一的接口。
当你需要访问一个聚合对象,而且不管这些对象是什么都需要遍历的时候,就应该考虑使用迭代器模式。另外,当需要对聚集有多种方式遍历时,可以考虑去使用迭代器模式。
聚合对象,如需要操作。则额外建立一个迭代器类。对集合进行操作处理,其实.net框架已经准备好了迭代器接口,只需要实现接口就行了IEumerator 支持对非泛型集合的简单迭代。
使用迭代器的好处就是在于保持良好的封装的同时对集合元素进行循环操作。不需要把数据集合展现给外部对象。不使用迭代器,外部对象会通过getter的方法获取数据集合然后遍历,这样把整个数据集合公开了,并且外部对象可以直接改写,另外遍历数据集合时也要知道数据结构,先分析数据结构才能进行迭代代码的书写,这样如果下次需要修改数据结构时,代码修改也会变的困难。
观察者模式
观察者模式又叫发布订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己的行为。
将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性,我们不希望为了维持一致性而使各个类紧密耦合,这样会给维护,扩展和重用都带来不便。而观察者模式的关键对象是主题Subject和观察者Obverser,一个Subject可以任意数目依赖它的Obverser,一旦Subject的状态发生变化,所有的Obverser都合一得到通知。Subject发出通知时,并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道,而任何一个具体的观察者不知道也不需要知道其他观察者的存在。
Subject类是把所以观察者对象的引用保存在一个聚集里,每个主体都可以有任意数量的观察者,抽象主体提供一个借口,可以增加和删除观察者对象.是个主题或者抽象统治者,Obverser类是抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,ConcreteSubject类是具体主题,讲有关状态存入具体观察者对象,在具体主题的内部状态改变时,给所有登记过的观察者发出通知,ConcreteObserver类是具体观察者,实现抽象观察者所要求的更新接口,一遍使本身的状态与主题的状态相协调
观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。
当一个对象的改编需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。需要将观察者与被观察者解耦或是观察者的种类不确定
选择关键点:观察者与被观察者是否是多对一的关系
中介者模式
定义了一个中介对象来封装一系列对象之间的交互关系。中介者使各个对象之间不需要显式地相互引用,从而使耦合性降低,而且可以独立地改变它们之间的交互行为
对象与对象之间存在大量的关联关系,这样势必会导致系统的结构变得很复杂,同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。同时若一个对象发生改变,我们也需要跟踪与之相关联的对象,同时做出相应的处理。系统的可扩展性低。增加一个新的对象,我们需要在其相关连的对象上面加上引用,这样就会导致系统的耦合性增高,使系统的灵活性和可扩展都降低。
封装一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,从而使其耦合性松散,而且可以独立地改变他们之间的交互
由此我们可以看出中介者对象封装了对象之间的关联关系,导致中介者对象变得比较庞大,所承担的责任也比较多,它需要知道各个对象交互的细节,如果它出现问题,将导致整个系统的瘫痪。故当系统中出现“多对多”的交互复杂的关系群时,千万别着急使用中介者模式,需要先分析下在设计上是否合理
主要包括抽象中介者和具体中介者 抽象同事类 和具体同事类
状态模式
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了
所谓的状态模式是允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类
状态模式主要解决的是当控制一个对象状态转换的条件表达式国语复杂时的情况,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断 很简单,那就没有必要用状态模式了。状态模式的好处就是将与特定状态相关的行为局部化,并且将不同状态的行为分隔开来
由于状态的改变,会改变派生类,产生不同的重载方法。
常用场景:一个对象在多个状态下行为不同,且这些状态可互相转换
选择关键点:这些状态是否经常在运行时需要在不同的动态之间相互切换
状态模式与职责链模式的区别:状态模式是让各个状态对象自己知道其下一个处理的对象是谁,即在编译时便设定好了的;而职责链模式中的各个对象并不指定其下一个处理的对象到底是谁,只有在客户端才设定。
策略模式
策略模式是针对一组算法,将每个算法封装到具有公共接口的独立的类中,从而使它们可以相互替换。策略模式使得算法可以在不影响到客户端的情况下发生变化。
定义了一个公共接口,各种不同的算法以不同的方式实现这个接口,Context使用这个接口调用不同的算法,一般使用接口或抽象类实现。
- 抽象策略角色:通常由一个接口或者抽象类实现。
- 具体策略角色:包装了相关的算法和行为。
- 环境角色:持有一个策略类的引用,最终给客户端调用。
应用场景:
- 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
责任链模式
责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。
某个请求需要多个对象进行处理,从而避免请求的发送者和接收之间的耦合关系。将这些对象连成一条链子,并沿着这条链子传递该请求,直到有对象处理它为止。这些对象由每一个对象对其下家的引用而连接起来形成一条链。通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推。
常用场景:一个请求的处理需要多个对象当中的一个或几个协作处理。
优点:降低耦合度。它将请求的发送者和接收者解耦。简化了对象。使得对象不需要知道链的结构。增加新的请求处理类很方便。增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
但是不能保证请求一定被接收。可能不容易观察运行时的特征,有碍于除错。
访问者模式
访问者模式就是表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
访问者适合于数据结构相对比较稳定的系统,主要是把处理从数据结构分离出来,很多系统可以按照算法和数据结构分开,如果这样的系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式就比较合适,因为访问者模式使得算法的增加变得容易。
访问者模式的优点是增加新的操作很容易,因为增加新的操作就意味这增加了一个新的访问者,访问者模式将有关的行为集中到一个访问者对象中
访问者的缺点其实是使增加新的数据结构变的很困难
备忘录模式
在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以把该对象恢复到原先的状态。发起人可以根据需要决定备忘录存储自己的哪些内部状态。
- 发起人:负责创建一个备忘录,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。发起人可以根据需要决定备忘录存储自己的哪些内部状态。
- 备忘录:负责存储发起人对象的内部状态,并可以防止发起人以外的其他对象访问备忘录。备忘录有两个接口:管理者只能看到备忘录的窄接口,他只能将备忘录传递给其他对象。发起人却可看到备忘录的宽接口,允许它访问返回到先前状态所需要的所有数据
- 管理者:负责备忘录,不能对备忘录的内容进行访问或者操作。
常用场景:需要在对象的外部保存该对象的内部状态
解释器模式
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
.当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树,可以使用解释器模式。而当存在以下情况时该模式效果最好
- 该文法的类层次结构变得庞大而无法管理。此时语法分析程序生成器这样的工具是最好的选择。他们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
- 效率不是一个关键问题,最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将他们装换成另一种形式,例如,正则表达式通常被装换成状态机,即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的
- 一些重复发生的问题,比如加减乘除四则运算,但是公式每次都不同,有时是a+b-c*d,有时是a*b+c-d,等等等等个,公式千变万化,但是都是由加减乘除四个非终结符来连接的,这时我们就可以使用解释器模式。
解释器模式真的是一个比较少用的模式,因为对它的维护实在是太麻烦了,想象一下,一坨一坨的非终结符解释器,假如不是事先对文法的规则了如指掌,或者是文法特别简单,则很难读懂它的逻辑。解释器模式在实际的系统开发中使用的很少,因为他会引起效率、性能以及维护等问题,尽量不要在重要模块中使用解释器模式,因为维护困难。在项目中,可以使用脚本语言来代替解释器模