关闭

设计模式学习笔记——行为模式

575人阅读 评论(0) 收藏 举报

1. 模板模式 Template
  作用:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中;
  详解:将一只大象装进冰箱要三步,开冰箱门->放进大象->关冰箱门<呵呵,这个例子太贴切,一下就想到了>(在一个抽象类里写处理业务的宏观流程,而将如何放进大象这样的方法放入抽象方法中交给继承这个抽象类的子类来实现);
  场合:一次性实现一个算法的不变部分,并将可变行为交给子类实现;
     各子类的行为应该被提取出来并集中到一个公共父类中以避免代码重复;
     起到控制子类扩展作用,因为模板方法只在特定点调用操作,这样就只能在这些点进行扩展。

2. 备忘录模式 Memento (or Token)
  作用:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态;
  详解:所谓不破坏封装性,指的是不暴露类的内部属性,由于我们需要在备忘录角色中保存备忘录发起者角色的属性,至少对于备忘录角色来说备忘录发起者角色的属性是公开的,我们所要做的是限制其它类知道这些,于是有几种方法可以做到这些:
     ①在c++中,使用友元声明;
     ②在java中,备忘录发起者角色中引入内部私有备忘录类;
     使用java的clone方法(不推荐,为备忘录发起者角色实现clone方法一定要非常慎重);
     使用两个不同的接口限制访问权限,宽接口提供比较完备的操作状态方法,窄接口仅提供标识,备忘录角色实现这两个接口,备忘录发起者角色采用宽接口进行访问,对其它角色或对象则采用窄接口进行访问(不推荐,必须使用人为的规范约束,约束力度小);
  场合:必须保存一个对象在某个时刻的(部分)状态,这样以后需要时它才能恢复到先前的状态;
     如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。

3. 观察着模式 Observer (发布-订阅模式 Publish/Subscribe)
  作用:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新;
  详解:主要有目标角色和观察者角色,目标角色角色是变化的主体,它包含更新自己状态的方法和一个增加观察者的方法,观察者角色接收到目标角色状态变化的消息后,更新自己<达到与时俱进哈哈>(目标角色类包含一个存放观察者的容器,增加观察者方法负责将具体观察者对象存放到目标对象的观察者容器中,更新自己状态方法中包含将变化发送给观察者的动作<可以用模板模式实现>;在客户端创建一个具体的目标角色对象后,需要调用增加观察者方法将具体观察者存放到目标对象的观察者容器中,这样,目标角色就知道要给谁在发送状态变化的消息了);
  场合:当一个抽象模式有两个方面,其中一个方面依赖于另一个方面。将这二者封装在独立的对象中以使它们可以各自独立的改变和服用;
     当对一个对象的改变需要同时改变其它对象,而不知道具体有多少对象有待改变;
     当一个对象必须通知其它对象,而它又不能假定其它对象是谁,换言之,你不希望这些对象是紧密耦合的;
  扩展:拉模式,当目标角色变化时,仅告诉观察着角色'我变化了',观察着角色如果要知道具体的变化细节,则要自己从目标角色的接口中得到;
     推模式,当目标角色变化时,通过参数将变化的细节传递到观察者角色中去。

4. 责任链模式 Chain of Responsibility (CoR)
  作用:使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一个链,并沿着这条链传递该请求,直到有一个对象处理它为止;
  详解:抽象处理角色接口包含一个处理请求方法;各个具体的处理角色实现抽象处理角色接口,并包含设置下一个具体处理请求对象的方法,在各个具体处理请求方法中,需要判断是否可以处理该请求,若能处理则调用处理方法,不能则调用下一个具体处理角色对象的处理请求方法(当然,要有下一个具体处理角色对象,若没有也可进行相应处理);
  场合:有多个对象可以处理一个请求,哪个对象处理请求运行时刻自动确定;
     你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求;
     可处理一个请求的对象集合应被动态指定;
  优点:降低了发送者类和接收者类之间的耦合性,提高了灵活性;
  缺点:效率低,因为一个请求可能要遍历所有处理对象,到最后才能被处理;
     扩展性差,因为在责任链模式中,一定要有一个统一的接口Handler;
  扩展:纯的责任链模式,规定一个具体的处理角色只能对请求做出两种动作,自己处理和传递给下家(如果有下家);反之就是不纯的责任链模式。

5. 命令模式 Command
  作用:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作;
  详解:抽象命令接口有一个execute方法,各个具体的命令类实现这个接口,定义各自的execute方法;客户端无需知道是什么具体的命令类型,只需要统一的调用命令接口的execute方法即可;
     初看就是普通的面向接口编程,只是将命令/请求变成了实现统一接口的对象给客户端调用而已,将普通方法变成具体命令对象类的一个方法;命令/请求对象化后,我们便可以对这个对象扩展功能,比如,我们可以在具体的命令类中增加undo方法,这样一个命令既可以正向的执行功能,也具备反向的取消功能,如果配合存储命令类的历史操作信息列表,我们就可以实现更为复杂的undo、redo功能了,这些,是仅用方法不容易实现的;
  场合:需要抽象出待执行的动作,然后以参数的形式提供出来——类似于过程设计中的回调机制,而命令模式正是回调机制的一个面向对象的替代品;
     在不同的时刻指定、排列和执行请求,一个命令对象可以有与初始请求无关的生存期;
     需要支持取消操作;
     支持修改日志功能,这样当系统崩溃时,这些修改可以被重做一遍;
     需要支持事务操作;
  优点:命令模式将调用操作的请求对象与知道如何实现该操作的接收对象解耦;
     具体命令角色可以被不同的请求角色重用;
     你可以将多个命令装配成一个复合命令;
     增加新的具体命令角色很容易,因为这无需改变已有的类。

6. 状态模式 State
  作用:允许一个对象在其内部状态改变时改变它的行为;
  详解:状态模式的核心是用实现了共同接口的各个具体状态类来表示各个状态,并将该状态的具体行为封装到这个具体状态类中,然后交由使用环境<Context>角色类来维护(使用环境角色类,它维护了一个具体状态角色类的对象,在状态切换方法中调用这个具体状态角色类的相应状态切换方法来达到切换状态的目的,调用具体状态角色类的返回本身状态的方法得到转化后的具体状态类对象;具体状态角色类都实现了共同的抽象状态角色接口,每个具体状态角色类实现各自的状态切换方法;客户端一般只调用使用环境角色就可以进行状态控制)
  优点:使用if else语句来进行代码响应选择,对于复杂一点的状态判断,就会变得杂乱无章,如果增加新的状态将会带来大量的代码修改;状态模式的引入避免了代码中复杂而庸长的的逻辑判断语句,而且由于其将具体状态与它对应的行为封装了起来,这使得增加一种新的状态变得简单;
  缺点:每个状态对应一个具体的状态类,使得整体分散,逻辑不够清晰;
  场合:一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为;
     一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态;
  扩展:尽量不将状态角色暴露给客户程序,但是当使用角色中的初始状态紧紧依赖于客户程序时,暴露仍在所难免;
     将使用环境角色作为参数传给具体状态角色后,地道的做法是在具体状态角色中调用使用环境角色的方法来实现状态相应行为。

7. 策略模式 Strategy
  作用:属于对象行为型设计模式,主要是定义一系列的算法,把这些算法一个个封装成拥有共同接口的单独的类,并且可以使他们之间可以互换;
  详解:策略模式包含使用环境<Context>角色、抽象策略<Strategy>角色和具体策略<Concrete Strategy>角色,不愿意多介绍详细实现方式,因为整个模式的结构和状态模式一致;
  辨别:状态模式与策略模式在结构上非常相似,但是在概念上,它们的目的差异非常大,策略模式是用来处理算法变化,而状态模式则是处理状态变化;
     区分这两个模式的关键是看行为是由状态驱动的还是由一组算法驱动的,通常,状态模式中的状态是在对象内部的,策略模式中的策略可以是在对象外部<非严格规律>;
     策略模式中,算法是否变化完全是由客户程序来决定的,而往往一次只能选择一种算法,不存在算法中间变化的情况;而状态模式中存在着状态的变化和行为的变更,而且状态变化是一个线性的整体,对客户程序而言,这种状态变化往往是透明的;
  场合:系统需要在几种算法中快速切换;
     系统中有一些类他们仅行为不同时,可以考虑采用策略模式来进行重构;
     系统中存在多重条件选择语句时,可以考虑采用策略模式来进行重构,但是注意策略模式不能同时使用多于一个的算法。

8. 调停者模式 Mediator (传递器模式)
  作用:用一个调停对象来封装一系列的对象交互(将对象间多对多的关系变为Mediator与对象间一对多的关系);
  详解:有一个Mediator接口用来定义成员对象之间的交互联系方式,Mediator接口的具体实现真正实现交互操作的内容,其包含各个成员对象,最后在成员对象类中包含一个Mediator对象;成员对象想要跟其它成员对象交互时,只能通过调用Mediator接口的各个交互方法来实现,这样成员对象相当于只和Mediator交互;
  辨别:由于调停者模式在定义上比较松散,在结构上和观察者模式、命令模式十分相像——都添加了中间件,只是调停者模式去掉了后两者在行为上的方向;另外观察者模式中的观察者、命令模式中的命令都是被客户端所知的,由客户来指定的,而大多调停者角色对于客户程序却是透明的,这样的区别是由于他们要达到的目的不同;
     调停者模式的应用目的又与结构模式中的“门面模式”有些相似,区别在于门面模式是介于客户程序与子系统之间的,而调停者模式是介于子系统与子系统之间的——门面模式是将原有的复杂逻辑提取到一个统一的接口,简化客户对逻辑的使用,它是被客户所感知的,而原有的复杂逻辑则被隐藏了起来;而调停者模式的加入并没有改变客户原有的使用习惯,它是隐藏在原有逻辑后面的,使得代码逻辑更加清晰、可用;
  场合:一组对象以定义良好但是复杂的方式进行通信,产生了混乱的依赖关系,也导致对象难以复用<当系统中出现了“多对多”的交互复杂的对象群,不要急于使用调停者模式,而要先反思系统在设计上是不是合理,设计良好的复杂通信才是使用这个模式的最好时机>。

9. 解释器模式 Interpreter
  作用:定义语言的文法,并且建立一个解释器来解释该语言中的句子<这里语言的意思是使用规定格式和语法的代码>;
  预备:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题;
     抽象语法树的每个节点都代表一个语句,而在每个节点上都可以执行解释方法,这个解释方法的执行就代表这个语句被解释,由于每个语句都代表一个常见问题的实例,因此每个节点上的解释操作都代表对一个问题实例的解答;
  详解:加、减、乘、除的例子中,我们有一个用于变量赋值和取值的全局上下文Comtext,有一个抽象表达式类,包含一个参数为Context的抽象解释方法,常量、变量和四个运算符都继承这个抽象表达式类,除变量外,其它具体表达式类实现自己的构造函数,它们都实现各自具体的解释方法,解释方法里调用Context的取值操作将变量还原为数值,并执行各个运算符应有的操作;客户程序产生常量和变量,并调用Context的变量赋值操作设定全局变量对应的值,然后通过显示调用运算符类的构造函数来搭建复杂的运算式,作为构造函数参数的常量和变量自动对应在运算式中的位置,要知道运算式的具体结果,我们只用调用最外层运算符的解释方法即可<过程那可是相当的复杂,对不?>;
  优点:解释器模式提供了一个简单的方式来执行语法,而且容易修改或扩展语法;
  缺点:解释器模式对于复杂文法难以维护,由于一个规则将对应一个处理类,而这些类还要递归调用抽象表达式类,多如乱麻的类交织在一起是多么恐怖的一件事啊<引用原文中的感慨……>。

10.访问者模式 Interpreter
  作用:表示一个作用于某个对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作;
  详解:通常,取出对象群结构中拿出的一个对象后,先确定对象的类型才能执行该类型的特有方法;
     访问者模式中,定义一个访问者接口,包含每种具体对象的访问方法,每个具体访问类实现这些访问方法;然后定义一个抽象对象接口,接口包含一个接收访问方法,具体对象都实现抽象对象接口并实现接口里的接受访问方法,在具体的接受访问方法里,通过调用具体访问类中与自己对应的访问方法;客户程序首先产生一个具体访问类对象,然后将此访问类对象作为参数传递给接受访问方法,此时接受访问方法会根据不同的具体对象匹配访问类对象中的访问方法,达到无须指定具体对象类型就能调用其特有方法的目的;
     双重分派:一次分派是在客户程序中将具体访问者模式作为参数传递给具体元素角色;二次分派是进入具体角色后,具体元素角色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去,具体访问者模式再根据参数的不同来选择方法执行;
  场合:简单的说,就是相对固定的数据结构(如类层次、各种具体元素对象的集合),数据结构对应的行为相对容易变动;
  优点:对原来类层次增加新的操作,仅仅需要实现一个具体的访问者角色即可,而不必修改整个类层次;
     每个具体的访问者角色对应一个相关操作,如果一个操作的需求有变,那么仅仅修改一个具体访问者角色,而不用改动整个类层次;
     由于访问者模式为我们的系统提供了一层“访问层”,因此我们可以在访问者中添加一些对元素角色的额外操作;
  缺点:如果系统中类层次发生了变化,必须修改抽象访问者角色和每一个具体的访问者角色;
     由于访问者角色要执行与具体元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,而在java中意味着其它的对象也可以访问,某种程度上来说破坏了元素角色的封装性;
     由于元素和访问者能够传递的信息有限,往往也会限制访问者模式的使用。

11.迭代器模式 Interpreter (游标模式 Cursor)
  作用:提供一种方法访问一个容器(container)对象中各个元素,而又不需暴露该对象的内部细节;
  详解:现在有各种不同的迭代器模式的实现方式,总的原则无外乎避免容器内部细节暴露和使设计符合单一职责原则;
     在java collection的应用中,是由客户程序来控制遍历的进程,被称为外部迭代器(相对于由迭代器自身控制迭代过程的内部迭代器,外部迭代器要灵活、强大很多,且在java语言中可用性也比较高);
     在java collection的应用中,提供的具体迭代器角色是定义在容器角色中的内部类,这样便保护了容器的封装,同时容器也提供了遍历算法的接口,可以扩充自己的迭代器;
     迭代器模式的使用,客户程序首先要得到具体容器角色,再通过具体容器角色得到具体迭代器角色,这样便可以使用具体迭代器角色来遍历容器<太废言了,想必是个人都用得烂熟了>;
  优点:支持以不同的方式遍历一个容器角色,根据实现方式不同,效果上有差别;
     简化了容器的接口,但是在java collection中为了提高可扩展性,容器还是提供了遍历的接口;
     对同一个容器对象,可以同时进行多个遍历,因为遍历状态是保存在每一个迭代器对象中的;
  场合:访问一个容器对象的内容而无需暴露它的内部表示;
     支持对容器对象的多种遍历;
     为遍历不同的容器结构提供一个统一的接口<多态迭代>。 

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:367196次
    • 积分:3938
    • 等级:
    • 排名:第8008名
    • 原创:35篇
    • 转载:242篇
    • 译文:2篇
    • 评论:70条
    最新评论