设计模式整理总结(三):行为型模式

三、行为型模式

  行为型模式主要包括:职责链(Chain of Responsibility)、命令(Command)、解释器(Interpreter)、迭代器(Iterator)、中介者(Mediator)、备忘录(Memento)、观察者(Observer)、状态(State)、策略(Strategy)、模板方法(Template Method)、访问者(Visitor),行为型模式共有11种。

3.1 职责链(Chain of Responsibility)模式

  定义:是多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
coresponse
  Handler类,引用下一个Handler,同时提供接口设置下一个Handler以实现后继链,还包括具体处理方式的抽象接口HandleRequest;ConcreteHandler处理它所负责的请求,可以访问它的后继者,即可以调用HandleRequest处理该请求,或者将请求转发给后继者。
  当有多个对象可以处理同一个请求的时候,哪个对象处理该请求由运行时候自动确定。这样使得接收者和发送者都没有对方明确的信息,且链中的对象自己也不知道链的结构,职责链可以简化对象的相互链接,他们仅需保持一个指向其后继者的引用就可以了,而不需要保持其所有候选接收者的引用。

3.2 命令(Command)模式

  定义:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
command
  源于行为请求者和行为实现者如果紧密耦合的话,虽然实现简单,但是极为的僵化。命令模式可以把请求一个操作的对象和知道怎么执行一个操作的对象分割开来。实践人家建议不要为代码急于实现命令模式,即使后面需要重构,也很容易实现它,只有真正需要记录、撤销、恢复等操作的功能时,才考虑实现命令模式。
  Command类抽象出一个Execute()接口;ConcreteCommand将一个接收者对象绑定于一个动作,然后调用接收者响应的操作,以实现Execute();Client创建一个具体命令对象并设定它的接收者;Invoker要求该命令执行这个请求;Receiver知道如何实现请求对应的具体操作,就是实现Action的内容。
  实例:Command模式很容易实现一个菜单Menu的功能,菜单的每一个MenuItem都是一个ConcreteCommand,当用户点击一个菜单项的时候,MenuItem调用Command规定的Execute()方法执行相应的操作。MenuItem本身并不知道他用的是哪个ConcreteMenu类,但是在创建MenuItem的时候已经通过构造函数放着请求的接收者,而Execute则可以直接调用接收者的一个或者多个操作。

3.3 解释器(Interpreter)模式

  定义:给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
interpreter
  如果一种特定类型的问题发生的频率足够的高,那么可能就值得将该问题的各个实例表达表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。解释器中的非终结符表达式定义相应子表达式的解释操作,而各终结符表达式的解释操作构成了递归的基础。
  AbstractExpression抽象表达式声明一个抽象的解释操作Interpret(),这个接口为抽象语法树中所有的节点锁共享;TerminalExpression和NonterminalExpression为终结符表达式和非终结符表达式,都继承自AbstractExpression并实现Interpret接口;Context包含解释器之外的一些全局信息。

3.4 迭代器(Iterator)模式

  定义:提供一种方法顺序访问一个聚合对象中各个元素,而又不需要暴露该对象的内部表示。而且可以提供对聚集的多种方式遍历,为遍历不同的聚集结构提供如开始、下一个、是否结束、当前哪一项等统一接口。
iterator
  Iterator声明访问和遍历元素的相关接口,如First()、Next()、IsDone()、CurrentItem()等接口;ConcreteIterator为具体迭代类,引用一个具体聚集对象,同时针对对象类型实现Iterator的操作接口,注意对该聚合遍历时候需要跟踪当前的位置信息;Aggregate提供Iterator对象的创建接口CreateIterator();ConcreteAggregate具体聚合类,实现创建相应迭代器的抽象接口CreateIterator()。
  总体来言,迭代器模式分离了集合对象的遍历行为,抽象出一个迭代器类来负责,这样既可以做到不暴露集合的内部结构,有可以让外部代码透明的访问结合内部的数据。

3.5 中介者(Mediator)模式

  定义:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式相互引用,从而使其耦合松散,而且可以独立地改变他们之间的交互。
mediator
  尽管将一个系统分割成许多对象通常可以增加其可复用性,不过对象间的相互关联的激增又反而会降低其可复用性。
  Mediator类为抽象中介者类,需要知道所有的具体同事类,定义一个借口用于与各同时对象通信,从具体的同事接收消息,向具体同时发送命令;Colleague是抽象同事类,ConcreteColleague是具体同事类,每个同事只知道自己的行为,而不了解其他同事的情况,但他们都认识中介者对象并保持一个对中介者的引用,当需要与其他同事通信的生活,与它的中介者通信。当然如果中介者需要扩展,可以扩展到具体的ConcreteMediator。
  当一组对下你个以定义良好但是复杂的方式进行通信,产生的相互依赖关系结构混乱难以理解,或者一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象的时候,可以考虑用中介者模式。由于把对象如何协作进行了抽象,将中介作为一个独立的概念并将其封装在一个对象中,这样关注的对象就从对象各自本身的行为转移到他们之间的交互上来了,就站在一个更宏观的角度上看待系统。但是由于ConcreteMediator控制了集中化,于是把交互的复杂性变为了中介者的复杂性,使得中介者变得比任何一个Colleague都复杂。
  实例比如:房东和租房者都可以作为信息的发送者和接收者,他们同时需要有设置中介的接口SetMediator,而中介需要提供接口setPerson来把信息发送者和接收者相关联,同时负责转发消息。

3.6 备忘录(Memento)模式

  定义:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
memento
  比如为了允许用户取消不确定的操作,或者支持从错误中恢复过来,需要实现检查点和取消机制,因此需要事先将状态信息保存在某处,这样才能支持对象的恢复操作。如果简单的提供一个接口让其它对象直接得到这些状态,将会暴露对象的细节并且破坏对象的封装性。
  Originator作为发起人内部包含状态信息,其负责创建一个Memento,用以记录保存当前时刻的状态,并支持用备忘录恢复其内部状态,发起人可以根据需要决定Memento可以存储Originator内部的哪些状态;Memento负责存储Originator内部的状态,并防止Originator以外其他对象访问备忘录,备忘录实际上有两个接口,其宽接口给Originator,可以访问恢复对象所需要的所有信息,窄接口给Caretaker(内部私有引用具体的memento),只能将备忘录传递给其他对象;Caretaker负责保存好备忘录,但不能对备忘录的内容进行操作或者检查。当需要保存部分的信息而不是全部信息Clone的时候,使用该模式。
  Memento模式比较适用于功能比较复杂,但需要维护或记录属性历史的类,或者需要保存的属性是众多属性中的一小部分,Originator可以根据保存在Caretaker中的Memento信息还原到以前的某个状态。
  实例比如:GameRole是一个发起者,其提供Save()和Load()接口可以创建Memento对象和加载Memento对象,CareTaker负责保存Memento对象,但是却不能访问其内容,可以发起保存memento或者在GameRole需要加载的时候取出memento。

3.7 观察者(Observer)模式

  定义:观察者模式通常也称为发布/订阅模式(Publish-Subsrcibe),其定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,当这个主题对象在状态发生变化时,所有依赖于它的对象都得到通知并被自动更新。
observer
  Subject主题或者抽象通知者,知道其观察者,通常将所有观察者对象的引用或指针保存在一个聚集observers里,每个主题可以有任意数量(多个)的观察者,同时提供可以增删观察者的接口;Observer抽象观察者,为那些在目标发生改变时需获得通知的对象定义一个更新接口Update();ConcreteSubject将有关状态存入subjectState,当它的状态发生改变的时候,给所有登记的观察者发出Notify通知;ConcreteObserver维护一个指向ConcreteSubject对象的引用,存储有关状态到observerState,这些状态应当同目标状态保持一致,实现Observer的更新接口,使自身状态与目标状态保持一致。
  将一个系统分割成一系列相互协作的类,其最大副作用就是需要维护相关对象的一致性,如果希望维护一致性而使得各个类相互紧密耦合,会对维护、扩展和重用带来不便。而观察者模式之关键对象包括Subject和Observer,一个Subject可以有任意数目的依赖他的Observer,一旦Subject的状态发生变化,所有的Observer都可以得到通知,当Subject发出通知的时候,并不需要知道它的观察者。
  当一个抽象模型有两个方面,其中一个方面依赖于另外一个方面,他们封装成独立的对象中可以增加使他们各自独立的改变和复用;同时当一个对象的改变需要通知其他对象的时候,而他不知道具体有多少对象有待改变的时候,这些情况都应该考虑使用观察者模式。
  实例比如:Blog作为主题,Observer作为订阅者。Blog提供Attach()、Remove()接口来添加删除订阅者并保存在list容器中,然后Notify()接口分别调用Blog中定义的Update()方法。前端中的数据和各个表格、饼状图等展示的关系,也可以使用观察者的模式来设计。

3.8 状态(State)模式

  定义:当一个对象的内在状态改变时允许改变其行为,对象看起来像是修改了它的类。
state
  Context类定义了用户感兴趣的Request()接口,不断处理请求、更改状态,同时引用维护一个ConcreteState子类的实例state,这个实例保存了当前状态,同时实现Request()接口,不断处理请求同时更改状态;State抽象状态类,定义一个接口Handle()以封装与Context的一个特定状态相关的行为;ConcreteState作为每一个子类实现一个与Context的一个状态相关的具体行为。
  状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂的时候,把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简单化。
  考虑使用状态模式的情况:
  (1) 一个对象的行为取决于它的状态, 并且它必须在运行时刻根据状态改变它的行为。
  (2) 一个操作中含有庞大的多分支的条件语句,且这些分支依赖于该对象的状态。
  因此状态模式就是为了消除庞大的条件分支和转移语句,将所有与状态相关的代码都存在于某个具体的ConcreteState类中,通过定义新的ConcreteState类可以轻松的增加新的状态和转移。
  实例比如:TCPConnection中的状态机,连接对象根据连接的状态(TCPState-TCPEstablished,TCPListen,TCPClosed)的不同表现出不同的行为操作。

3.9 策略(Strategy)模式

  定义:定义了一个算法家族,把他们一个个封装起来,让它们之间可以互相替换。此模式让算法的变化,不会影响到使用算法的客户,使得算法可独立于使用它的客户而变化。
strategy
  Strategy定义所有支持的算法的公共接口AlgorithmInterface(),Context使用这个接口来调用某个具体的ConcreteStrategy定义的算法;ConcreteStrategy具体算法类实现这个接口产生某具体算法;Context类用一个具体的ConcreteStrategy对象来配置,维持一个对Strategy对象的引用strategy,并定义一个接口来让Strategy类访问它的数据,接口ContextInterface()通过调用具体的strategy.AlgorithmInterface()实现功能而不影响客户的使用。
  策略模式是一种定义一系列算法的方法,从概念上说这些算法完成的都是相同的工作,只是实现不同而已。他们可以以相同的方式调用所有的算法,减少各种算法与使用算法类之间的耦合。
  策略模式的Strategy类层次为Context定义了一系列可供重用的算法或行为,其作为父类的继承关系有助于汲取出这些算法中的公共功能。当不同的行为堆砌到一个类里面,难免会使用条件语句选择合适的行为,而将这些行为封装在独立的Strategy类里面,可以用于消除这些条件语句,源于在策略模式中,选择所需具体实现的职责,由客户端对象承担,并转嫁给Context对象了。 策略模式在使用的时候,要么创建好具体的算法对象传递进去,要不使用名字、标签等形式来指定算法类型,而在C++中,也可以使用模板参数的方式直接实例化需要的类型。

3.10 模板方法(Template Method)模式

  定义:定义一个操作中的算法的骨架,而将一些步骤延迟到子类当中,模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
template-method
  AbstractClass抽象模板负责定义抽象的原语操作,具体的子类将重新定义它们以实现一个算法的各步骤,同时实现一个一个模板方法,还需要定义一个算法的骨架,该模板方法不仅可以调用原语操作,也调用定义在AbstractClass或其他对象中的操作(反正都会被ConcreteClass所继承);ConcreteClass实现原语操作以完成算法中与特定子类相关的步骤。每个AbstractClass可以有任意多个ConcreteClass与之对应,而每个ConcreteClass都可以给出这个抽象方法的不同实现。
  当使用了继承体系,并且肯定这个继承有意义,就应该将所有重复的代码都应该上升到父类去,而不是让每个子类都去重复。当需要完成一系列的步骤或者过程,但个别步骤(上文的原语)在更详细的实现上可能不同的话,通常应该使用模板方法的模式来处理。也就是一次性实现一个算法的不可变部分,并将可变的行为留给子类来实现。
  模板的方法通过把不变的行为移到超类,去除子类中的重复代码,从而实现一个好的代码复用平台。而当不可变和可变的行为在方法的子类中混合在一起的时候,不变的行为就会在子类中重复出现,需要通过模板方法把这些行为移动到单一的地方。
  实例比如:填写简历,可以抽象出Resume类并定义FillResume()进行创建过程(比如SetPersonalInfo、SetEducation等),然后在Resume的各个派生类中实现构造过程的具体细节。

3.11 访问者(Visitor)模式

  定义:表示一个作用于某对象结构中的各元素的操作,它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
visitor
  Visitor为该对象结构中ConcreteElement的每一个类声明一个Visit操作,该操作的名字和特征标识了发送Visit请求给该访问者的哪个类;ConcreteVisitor1,2为具体访问者,实现每个由Visitor声明的操作,每个操作实现算法的一部分;Element定义了一个Accept()操作,它以一个Visitor访问者作为参数;ConcreteElementA,B实现具体的Accept()操作;ObjectStructure能枚举它的元素,可以提供一个高层的接口允许访问者访问他的元素,他可以是一个Composite或是一个集合,比如一个列表或者一个无序集合。
  访问者模式的目的是要把处理从数据结构中分离出来,很多系统是数据结构和算法分开,如果系统有比较稳定的数据结构,又有易于变化的算法的话,使用访问者模式比较的合适,访问者模式让算法操作的增加变得容易,因为新增加的操作就等于新增加一个访问者类。但是访问者模式d的缺点是增加新的数据结构变的困难了。

本文完!

参考