设计模式——行为型模式

前言:本文为学习《大话设计模式》的总结。其它参考链接有:
https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/behavioral.html

总结:行为型模式共11种,策略模式、模板方法模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式

一、命令模式

在软件设计中,我们经常需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来进行设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。

命令模式可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。这就是命令模式的模式动机。

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

Command: 抽象命令类
ConcreteCommand: 具体命令类
Invoker: 调用者
Receiver: 接收者
Client:客户类
在这里插入图片描述
在这里插入图片描述
命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。

每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。

命令模式的优点

降低系统的耦合度。
新的命令可以很容易地加入到系统中。
可以比较容易地设计一个命令队列和宏命令(组合命令)。
可以方便地实现对请求的Undo和Redo。
可以较容易地将命令记入日志

在以下情况下可以使用命令模式:

系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
系统需要在不同的时间指定请求、将请求排队和执行请求。
系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
系统需要将一组操作组合在一起,即支持宏命令

很多系统都提供了宏命令功能,如UNIX平台下的Shell编程,可以将多条命令封装在一个命令对象中,只需要一条简单的命令即可执行一个命令序列,这也是命令模式的应用实例之一。
宏命令又组合命令 , 它是命令模式和组合模式联用的产物。
宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员对象可以是简单命令,还可以继续是宏命令

实例: 例如电视机与遥控器命令, 可以设计为命令模式 。

二、策略模式

策略模式(Strategy Pattern):定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。

策略模式是用来封装算法的,但是实践中,可以封装几乎任何类型的规则,只在听到需要在不同时间应用不同的业务规则,可以考虑使用策略模式。

类:分类的基础是抽象,具有相同属性和功能的抽象集合才是类。

Context: 环境类
Strategy: 抽象策略类
ConcreteStrategy: 具体策略类
在这里插入图片描述
在这里插入图片描述

策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。

基本的策略模式中,选择所用具体实现的职责由客户端对象承担, 可以在上下文类中, 以简单工厂的思想,将客户端需要做的判断迁移到上下文类中。 但这依然没有省去判断的Switch分支。 可以考虑用反射机制。 ( C++ 中还不支持反射机制)

策略模式主要优点在于对“开闭原则”的完美支持,在不修改原有系统的基础上可以更换算法或者增加新的算法,它很好地管理算法族,提高了代码的复用性,是一种替换继承,避免多重条件转移语句的实现方式;其缺点在于客户端必须知道所有的策略类,并理解其区别,同时在一定程度上增加了系统中类的个数,可能会存在很多策略类。

三、中介者模式

迪米特法则: 如果两个类不必彼此通信,那么两个类就不应该发生直接的相互作用。 如果一个类需要调用另一个类的方法的话,应该通过第三者转发这个调用。

中介者模式定义: 用一个中介对象来封装一系列的对象交互 ,中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可独立地改变它们之间的交互 。

Mediator: 抽象中介者
ConcreteMediator: 具体中介者
Colleague: 抽象同事类
ConcreteColleague: 具体同事类
在这里插入图片描述
在这里插入图片描述

中介者承担两方面的职责:

中转作用(结构性):通过中介者提供的中转作用,各个同事对象就不再需要显式引用其他同事,当需要和其他同事进行通信时,通过中介者即可。该中转作用属于中介者在结构上的支持。
协调作用(行为性):中介者可以更进一步的对同事之间的关系进行封装,同事可以一致地和中介者进行交互,而不需要指明中介者需要具体怎么做,中介者根据封装在自身内部的协调逻辑,对同事的请求进行进一步处理,将同事成员之间的关系行为进行分离和封装。该协调作用属于中介者在行为上的支持

想通过一个中间类来封装多个类中的行为,而又不想生成太多的子类。可以通过引入中介者类来实现,在中介者中定义对象。
或是应用于一组对象以定义良好但是复杂的方式进行通信的场合。

中介者模式的缺点
在具体中介者类中包含了同事之间的交互细节,可能会导致具体中介者类非常复杂,使得系统难以维护。

中介者模式与GUI开发
中介者模式可以方便地应用于图形界面(GUI)开发中,在比较复杂的界面中可能存在多个界面组件之间的交互关系。
对于这些复杂的交互关系,有时候我们可以引入一个中介者类,将这些交互的组件作为具体的同事类,将它们之间的引用和控制关系交由中介者负责,在一定程度上简化系统的交互,这也是中介者模式的常见应用之一

实例: 例如Qt中的事件机制, 通过widget的事件管理机制进行事件分发及过滤给各子控件。 事件管理机制及一个复杂的中介者

四、观察者模式

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

Subject: 目标
ConcreteSubject: 具体目标
Observer: 观察者
ConcreteObserver: 具体观察者
在这里插入图片描述
在这里插入图片描述
一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
一个对象必须通知其他对象,而并不知道这些对象是谁。
需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

五、状态模式

定义: 当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况,把状态的判断逻辑转移到不同状态的一系列类中,可以把复杂的判断逻辑简化。

状态模式的好处是将与特定状态相关的行为局部化, 并且将不同状态的行为分割开来。

它将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个具体状态中,所以通过定义新的子类可以很容易增加新的状态和转换。
状态模式通过将各种状态转移逻辑分布到STATE的子类之间, 减少相互间的依赖。

适用场景: 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑用状态模式了。
如果业务需求某项业务有多个状态,通过都是一些枚举常量,状态的变化都是依靠大量的分支判断语句实现,也可以考虑将每一种业务状态定义为一个STATE 的子类。这样对象就可以不依赖于其它对象而独立变化了。

Context: 环境类
State: 抽象状态类
ConcreteState: 具体状态类
在这里插入图片描述
在这里插入图片描述

状态模式就是状态机思想

示例: TCP
在这里插入图片描述
在这里插入图片描述
状态模式没有严格的遵循开闭原则 ,增加新的状态和转换需要对相关的状态类进行一定的修改。

六、模板方法模式

当我们要完成在某一细节层次一致和一个过程或一系列步骤,但其个别步骤在更详细的层次上的实现可能不同时, 我们通常考虑用模板方法模式来处理。

定义: 定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。 模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

模板方法模式提供了一个很好的代码复用平台。
当不变和可变的行为在方法的子类实现中混合在一起的时候,不变的行为会在子类中重复。 通过模板方法模式可以把这些行为搬移到父类中。 减少重复性。

在这里插入图片描述

七、备忘录模式Memento

定义 : 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

备忘录模式比较适用于功能比较复杂的,但需要或记录属性历史的类,或需要保存的属性只是众多属性中的一小部分时,Originator可以根据Memento信息还原到前一状态 。

如果在某个系统中使用命令模式时,需要实现命令的撤销功能,那么命令模式可以使用备忘录模式来存储可撤销操作的状态。

有时一些对象的内部信息需要保存在对象以外的地方,但是必须由对象自己读取,这时,使用备忘录可以把复杂的对象内部信息对其他的对象屏蔽起来,恰当地保持封装的边界。

当角色的状态改变时,有可能这个状态无效,这时候就可以使用暂存的备忘录将原来的信息复原。
在这里插入图片描述

八、迭代器模式 Iterator

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

当需要访问一个聚焦对象,而且不管这些对象是什么都需要遍历的时候,可以考虑迭代器模式。
不过好多高级语言已经把迭代器模式做在语言中了。

迭代器模式分离了集合对象的遍历行为,抽象出一个迭代器类来负责,既不暴露集合的内部结构,又可让外部代码透明地访问集合内部的数据。

九、职责链模式

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

适用场景: 考虑公司中请假或加薪的层层审批结构
考虑Qt中事件处理机制中的层层传递

在这里插入图片描述
发出请求的客户端并不知道当中的哪一个对象最终处理这个请求,这样系统的更改可以在不影响客户端的情况下动态地重新组织和分配责任。

ConcreteHandler,具体处理者类,负责处理它所负责的请求,可以访问它的后继者,如果可处理该请求,就处理,否则就将该请求转发它的后继者。

接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。 它们可简化对象的连接,仅需保持一个指向其后继者的引用 ,而不需保持所有对象 。

可以随时增加或修改处理一个请求的结构。

不过要注意的是避免一个请求到了末端都得不到处理。

在责任链中, 请求都是由链头发起的 , 即ConcreteHandler1

十、解释器模式

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

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

例子: 正则表达式
非终结符表达式为文法中的非终结符实现解释操作, 对文法中每一条分规则都做出解释。

十一、访问者模式

定义:表示一个作用于某对象结构中各元素的操作,它使你可以在不改变各元素的类的前提下自定义作用于这些元素的新操作。

访问者模式使用的前提是: 对象数据结构要稳定。
它把数据结构和作用于数据结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。

适用场景是: 相对稳定的数据结构 + 易于变化的算法。 这样可以方便增加算法操作。

它的缺点是使得增加新的数据结构变得困难了。
在这里插入图片描述

访问者模式中关键的一点是双分派技术:

void Accept(Visitor visitor)
{
visitor.VisitConcreteElementA(this);
}

客户端将具体访问者作为参数传递给具体数据结构完成第一次分派
具体数据结构调用作为参数的访问者的访问方法,并把自己(this)作为参数传递进去。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值