设计模式——设计模式选择——行为型

本文详细介绍了设计模式中的行为型模式,包括职责链模式、命令模式、解释器模式、迭代器模式和访问者模式。这些模式主要用于封装变化、对象行为的分配以及解耦发送者和接收者。例如,职责链模式通过链式结构使得多个对象有机会处理请求,命令模式通过将请求封装为对象实现参数化,解释器模式用于解析特定语言,迭代器模式提供遍历聚合对象的接口,访问者模式则允许在不修改元素类的情况下定义作用于这些元素的新操作。
摘要由CSDN通过智能技术生成

对象行为型模式

Chain Of Responsibility——职责链模式

意图

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

动机

考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分上点击就可以得到帮助信息,所提供的帮助依赖于点击的是界面的哪一部分以及其上下文。例如,对话框中的按钮的帮助信息就可能和主窗口中类似的按钮不同。如果对那一部分界面没有特定的帮助信息,那么帮助系统应该显示一个关于当前上下文的较一般的帮助信息一比如说,整个对话框。

因此很自然地,应根据普遍性(generality)即从最特殊到最普遍的顺序来组织帮助信息。而且,很明显,在这些用户界面对象中会有一个对象来处理帮助请求;至于是哪一个对象则取决于上下文以及可用的帮助具体到何种程度。

这儿的问题是提交帮助请求的对象(如按钮)并不明确知道谁是最终提供帮助的对象。我们要有一种办法将提交帮助请求的对象与可能提供帮助信息的对象解耦(decouple)。Chain of Responsibility模式告诉我们应该怎么做。

这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者。该请求沿对象链传递直至其中一个对象处理它。

适用性

在以下条件下使用Responsibility链:

  • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可处理个请求的对象集合应被动态指定。

结构

这里写图片描述

参与者

  • Handler
    定义一个处理请求的接囗。
    实现后继链(可选)。
  • ConcreteHandler
    处理它所负责的请求。
    可访它的后继者。
    如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
  • Client
    向链上的具体处理者(ConcreteHandler)对象提交请求。

协作

当客户提交一个请求时,请求沿链传递直至有一个ConcreteHandler对象负责处理它。

效果

Responsibility链有下列优点和缺点:

降低耦合度该

模式使得一个对象无需知道是其他哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。

结果是,职责链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。

增强了给对象指派职责的灵活性

当在对象中分派职责时,职责链给你更多的灵活性。你可以通过在运行时刻对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。

不保证被接受

既然一个请求没有明确的接收者,那么就不能保证它一定会被处理——该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。

实现

下面是在职责链模式中要考虑的实现问题:

实现后继者链

有两种方法可以实现后继者链。

  • 定义新的链接(通常在Handler中定义,但也可由ConcreteHandlers来定义
  • 使用已有的链接。

我们的例子中定义了新的链接,但你常常可使用已有的对象引用来形成后继者链。例如,在一个部分一整体层次结构中,父构件引用可定义一个部件的后继者。窗口组件(Widget)结构可能早已有这样的链接。Composite更详细地讨论了父构件引用。

当已有的链接能够支持你所需的链时,完全可以使用它们。这样你不需要明确定义链接,而且可以节省空间。但如果该结构不能反映应用所需的职责链,那么你必须定义额外的链接。

连接后继者

如果没有已有的引用可定义一个链,那么你必须自己引人它们。这种情况下Handler不仅定义该请求的接口,通常也维护后继链接。这样Handler就提供了HandleRequest的缺省实现:HandleRequest向后继者〔如果有的话)转发请求。如果ConcreteHandler子类对该请求不感兴趣,它不需重定义转发操作,因为它的缺省实现进行无

表示请求

可以有不同的方法表示请求。最简单的形式,比如在HandleHelp的例子中,请求是一个硬编码的(hard-coded)操作调用。这种形式方便而且安全,但你只能转发Handler类定义的固定的一组请求。

另一选择是使用一个处理函数,这个函数以一个请求码(如一个整型常数或一个字符串)为参数。这种方法支持请求数目不限。唯一的要求是发送方和接受方在请求如何编码问题上应达成一致。

这种方法更为灵活,但它需要用条件语句来区分请求代码以分派请求。另外,无法用类型安全的方法来传递请求参数,因此它们必须被手工打包和解包。显然,相对于直接调用一个操作来说它不太安全。

为解决参数传递间题,我们可使用独立的请求对象来封装请求参数。Request类可明确地描述请求,而新类型的请求可用它的子类来定义。这些子类可定义不同的请求参数。处理者必须知道请求的类型(即它们正使用哪一个Request子类)以访同这些参数。

为标识请求,Request可定义一个访问器(accessor)函数以返回该类的标识符。或者,如果实现语言支持的话,接受者可使用运行时的类型信息。

需要考虑增加内容

相关模式

职责链常与Composite一起使用。这种情况下,一个构件的父构件可作为它的后继。

Command(Action、Transaction)——命令模式

意图

将一个请求封装为一个对象.从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。

动机

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输人。但工具箱不能显式的在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。而工具箱的设计者无法知道请求的接受者或执行的操作。

命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并像其他的对象一样被传递。这一模式的关键是一个抽象的Command类,它定义了一个执行操作的接口。其最简单的形式是一个抽象的Execute操作。具体的Command子类将接收者作为其一个实例变量,并实现Execute操作指定接收者采取的动作。而接收者有执行该请求所需的具体信息。

适用性

当你有如下需求时,可使用Command模式:

  • 像上面讨论的Menultem对象那样,抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(callback)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。command模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那儿实现该请求。
  • 支持取消操作。Command的Excute操作可在实施操作前将状态存储起来。在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用Unexecute和Execute来实现重数不限的“取消”和“重做”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在Command接口中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读人记录下来的命令并用Execute操作重新执行它们。
  • 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务(transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command模式提供了对事务进行建模的方法。Command有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

结构

这里写图片描述

参与者

  • Command
    声明执行操作的接口。
  • ConcreteCOmmand
    将一个接收者对象绑定于一个动作。
    调用接收者相应的操作,以实现Execute。
  • Client
    创建一个具体命令对象并设定它的接收者。
  • Invoker
    要求该命令执行这个请求。
  • Receiver
    知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。

协作

  • Client创建一个Concretecommand对象并指定它的Receiver对象
  • 某Invoker对象存储该Concretecommand对象。
  • 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤消的,Concretecommand就在执行Excute操作之前存储当前状态以用于取消该命令。
  • ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求。

效果

Command模式有以下效果.

  1. Command模式将调用操作的对象与知道如何实现该操作的对象解耦。
  2. Command是头等的对象。它们可像其他的对象一样被操纵和扩展。
  3. 你可将多个命令装配成一个复合命令。例如是前面描述的MacroCommand类。一般说来,复合命令是Composite模式的一个实例。
  4. 增加新的Command很容易,因为这无需改变已有的类。

实现

实现Command模式时须考虑以下问题:

一个命令对象应达到何种智能程度

命令对象的能力可大可小。一个极端是它仅确定一个接收者和执行该请求的动作。另一极端是它自己实现所有功能,根本不需要额外的接收者对象。当需要定义与已有的类无关的命令,当没有合适的接收者,或当一个命令隐式地知道它的接收者时,可以使用后一极端方式。例如,创建另一个应用窗口的命令对象本身可能和任何其他的对象一样有能力创建该窗口。在这两个极端间的情况是命令对象有足够的信息可以动态的找到它们的接收者。

支持取消(undo)和重做(redo)

如果Command提供方法逆转(reverse)它们操作的执行(例如UnExecute或Undo操作),就可支持取消和重做功能。为达到这个目的,Concretecommand类可能需要存储额外的状态信息。这个状态包括:

  • 接收者对象,.它真正执行处理该请求的各操作。
  • 接收者上执行操作的参数。
  • 如果处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态。

若应用只支持一次取消操作,那么只需存储最近一次被执行的命令。而若要支持多级的取消和重做,就需要有一个己被执行命令的历史表列,该表列的最大长度决定了取消和重做的级数。历史表列存储了已被执行的命令序列。向后遍历该表列并逆向执行命令是取消它们的结果;向前遍历并执行命令是重执行它们。

有时可能不得不将一个可撤销的命令在它可以被放人历史列表中之前先拷贝下来。这是因为执行原来的请求的命令对象将在稍后执行其他的请求。如果命令的状态在各次调用之间会发生变化.那就必须进行拷贝以区分相同命令的不同调用。

例如,一个删除选定对象的删除命令(DeleteCommand)在它每次被执行时,必须存储不同的对象集合。因此该删除命令对象在执行后必须被拷贝,并且将该拷贝放人历史表列中。如果该命令的状态在执行时从不改变,则不需要拷贝,而仅需将一个对该命令的引用放人历史表列中。在放人历史表列中之前必须被拷贝的那些Command起着原型(参见Prototype模式)的作用。

避免取消搡作过中的误积累

在实现一个可靠的、能保持原先语义的取消/重做机制时,可能会遇到滞后影响问题。由于命令重复的执行、取消执行,和重执行的过程可能会积累错误,以至一个应用的状态最终偏离初始值。这就有必要在Command中存人更多的信息以保证这些对象可被精确地复原成它们的初始状态。这里可使用Memento模式来让该Command访问这些信息而不暴露其他对象的内部信息。

使用c++模板(修改)

对(1)不能被取消〔2)不需要参数的命令,我们可使用c++模板来实现,这样可以避免为每一种动作和接收者都创建一个Command子类。

相关模式

Composite模式可被用来实现宏命令。
Memento模式可用来保持某个状态,命令用这一状态来取消它的效果。
在被放人历史表列前须被拷贝的命令起到一种原型的作用。

Interpreter——解释器模式

意图

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

动机

如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

例如,搜索匹配一个模式的字符串是一个常见问题。正则表达式是描述字符串模式的一种标准语言。与其为每一个的模式都构造一个特定的算法,不如使用一种通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了待匹配字符串的集合。

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及
如何解释这些句子。

适用性

当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
- 该文法简单对于复杂的文法,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
- 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。

结构

这里写图片描述

参与者

  • AbstractExpression
    声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。
  • TerminalExpression
    实现与文法中的终结符相关联的解释操作。
    一个句子中的每个终结符需要该类的一个实例。
  • NonterminalExpreSSion
    对文法中的每一条规则R::=R1 R2 … Rn 都需要一个NonterrmnalExpression类。
    为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。
    为文法中的非终结符实现解释(Interpret)操作。解释(Interpret)一般要递归地调用表示R1到Rn的那些对象的解释澡作。
  • Context
    包含解释器之外的一些全局信息。
  • Client
    构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
    调用解释操作。

协作

  • Client构建(或被给定)一个句子,它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树.然后初始化上下文并调用解释操作。
  • 每一非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。
  • 每一节点的解释操作用上下文来存储和访问解释器的状态。

效果

解释器模式有下列的优点和不足:

易于改变和扩展文法

因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。已有的表达式可被增量式地改变,而新的表达式可定义为旧表达式的变体。

也易于实现文法

定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个编译器或语法分析程序生成器自动生成。

复杂的文法难以维护

解释器模式为文法中的每一条规则至少定义了一个类(使用BNF定义的文法规则需要更多的类)。因此包含许多规则的文法可能难以管理和维护。可应用其他的设计模式来缓解这一问题。但当文法非常复杂时,其他的技术如语法分析程序或编译器生成器更为合适。

实现

Interpreter和Composite模式在实现上有许多相通的地方。下面所要考虑的一些特殊问题:

创建抽象语法树

解释器模式并未解释如何创建一个抽象的语法树。换言之,它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来生成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接由Client提供。

定义解释搡作

并不一定要在表达式类中定义解释操作。如果经常要创建一种新的解释器,那么使用Visitor模式将解释放人一个独立的“访问者”对象更好一些。例如,一个程序设计语言的会有许多在抽象语法树上的操作,比如类型检查、优化、代码生成,等等。恰当的做法是使用一个访问者以避免在每一个类上都定义这些操作。

与Flyweight模式共享终结符

在一些文法中,一个句子可能多次出现同一个终结符。此时最好共享那个符号的单个拷贝。计算机程序的文法是很好的例子——每个程序变量在整个代码中将会出现多次。

终结节点通常不存储关于它们在抽象语法树中位置的信息。在解释过程中,任何它们所需要的上下文信息都由父节点传递给它们。因此在共享的(内部的)状态和传人的(外部的)状态区分得很明确,这就用到了Flyweight模式。

相关模式

Composite模式:抽象语法树是一个复合模式的实例。
Flyweight模式:说明了如何在抽象语法树中共享终结符。
Iterator:解释器可用一个迭代器遍历该结构。
Visitor:可用来在一个类中维护抽象语法树中的各节点的行为。

Iterator(Cursor)——迭代器模式

意图

提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。

动机

一个聚合对象,如列表(list),应该提供一种方法来让别人可以访问它的元素,而又不需暴露它的内部结构,此外,针对不同的需要,可能要以不同的方式遍历这个列表。但是即使可以预见到所需的那些遍历操作,你可能也不希望列表的接口中充斥着各种不同遍历的操作。有时还可能需要在同一个表列上同时进行多个遍历。

迭代器模式都可帮你解决所有这些问题。这一糗式的关键思想是将对列表的访问和遍历从列表对象中分离出来并放人一个迭代器(iterator)对象中。迭代器类定义了一个访问该列表元素的接口。迭代器对象负责跟踪当前的元素;即,它知道哪些元素己经遍历过了。

适用性

迭代器模式可用来:

  • 访问一个聚合对象的内容而无需暴露它的内部表示。
  • 支持对聚合对象的多种遍历。
  • 为遍历不同的聚合结构提供一个统一的接口(即,支持多态迭代)。

结构

这里写图片描述

参与者

  • Iterator
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mr___Ray

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值