0%

责任模式

行为模型模式

责任链

概述

责任链就是 将所有对象按照链条的形式串联,请求按照顺序从这个链条的起始对象开始往后传递,如果遇到一个对象可以处理该消息,那么处理返回,否则就将消息传递给它指向的下一个对象。

结构

实现上,每个类别存放一个指向下一个对象的指针(类似单向链表)。子类要重写基类的处理函数Handle,子类中判断该消息是否可以处理,如果能就处理然后返回;如果不能,就传递给指向的下一个对象执行,这个操作逻辑可以直接调用基类的Handle执行,在基类中完成向下一个对象传递消息并执行的逻辑。

命令

概述

???

将命令封装成对象中,具有以下作用:

  • 使用命令来参数化其它对象
  • 将命令放入队列中进行排队
  • 将命令的操作记录到日志中
  • 支持可撤销的操作

结构

  • Command:命令
  • Receiver:命令接收者,也就是命令真正的执行者
  • Invoker:通过它来调用命令
  • Client:可以设置命令与命令的接收者

image-20210522221413603

迭代器

概述

提供一种顺序访问聚合对象元素的方法,并且不暴露聚合对象的内部表示。这样不同类型的容器就可以使用统一的迭代器进行容器内部元素的按序访问。这个在stl库里很常用

结构

  • Aggregate 是聚合类,其中createIterator() 方法可以产生一个 Iterator;
  • Iterator主要定义了 hasNext()next()方法;
  • Client 组合了 Aggregate,为了迭代遍历 Aggregate,也需要组合 Iterator

image-20210523165653286

中介者

概述

该模式中,建议将对象之间的复杂的沟通和控制方式转换为简单的星形方式。所有组件都通过中介对象向其他组件发送命令。

image-20210524195212430

结构

有点像 qt 中的信号和槽的机制。实现上,首先每个组件 都需要继承包含中介对象指针,每个组件都要有设置中介对象的接口。然后中介对象中有一个 notify() 函数,某个组件想要通知另一个组件干什么事儿,就调用自己类里的 中介对象的 notify() 函数向中介发送消息。然后在 notify函数中根据消息类型来处理事件。初始的时候,将所有组件注册进中介对象,以方便中介对象 notify() 函数中能够操作任意连接的对象。

image-20210524200417504

备忘录

概述

允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。说白了对历史存快照,并 实现撤回。

结构

  • 原始对象:需要对原始对象中表示状态的 公有/私有成员变量做 备份
  • Memento: 备忘录对象,将原始对象中的状态变量封装在这个类中,作为和外界(Caretaker)沟通的状态包。
  • Caretaker:负责人,将历史状态包保存为数组,用于撤回功能,它无法访问 Memento 中封装的状态对象

为何会有单独的这个模式?如果要是实现撤回,必然要对对象的状态做保存,但是对象中的成员变量可能有的是私有的变量,外界(按下保存状态按钮后)无法访问,也就无法获取状态。这个模式就是建议,新增一个备忘录对象作为 原始对象 和 Caretaker负责人沟通的桥梁,原始对象通过 save 接口将自身状态相关变量封装在 备忘录对象中 输出,给负责人存下。负责人将历史的 备忘录对象 给原始对象的 restore 接口用于恢复对象。(getState函数可以设置为友元函数,这样只有红框里的是一起的可以调用)

image-20210524204959467

观察者

概述

定义对象之间的一对多依赖,当一个对象状态改变时,它的所有依赖都会收到通知并且自动更新状态。

结构

  • 发布者对象:发布者状态更新后,通知与它相连的所有观察者状态发生了变化
  • 观察者对象:有多个观察者对象,对发布者 “侦听”

实现起来很简单,每个类型的观察者都要重写 upadate函数,就是在 发布者对象中设置一个链表(或者集合),保存所有的观察者对象的指针,更新的时候就挨个调用观察者对象中的 upadate函数 类似回调函数吧。

状态模式

概述

一般的有限状态机的写法 有许多 判别语句,这个模式可以避免写判断语句。这个模式 将每个状态设置为一个单独的状态对象,通过切换不同的状态独享来切换他们的行为。

状态模式让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样

结构

状态 抽象为一个接口,然后每个状态都对接口(即触发状态转移的条件,图中的 request )重新实现 , 不同状态对象,就会调用它重新实现的触发函数,并在其中将状态修改,当前状态使用一个上下文对象保存即可,状态切换实质就是将 基类状态指针指向不同的 实例对象,不同的实例对象又对状态转移条件接口有不同的实现,这就完成了 根据不同状态,不同的状态转移条件 做状态切换的逻辑

image-20210524213503558

本质上的原理是通过多态性质来完成不同状态下的不同方法。考虑下面简单的状态转移,A状态 在 request 条件下 切换成 B 状态,B状态又在request 条件下切换为A状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
//  一般的 SWITCH CASE  语句写有限状态机
string state = "A";
switch state{
case "A":
if(request) state = "B"; break;
case "B":
if(request) state = "A"; break;
}
// 状态模式中的写法
// 将每个状态 中的状态转移条件抽象为一个结构,然后每个状态都对接口重新实现,就表明了 在当前状态下 request条件发生时应该进行的操作
class Context;
class state{
protected:
Context* context; // 保存状态上下文 使用该对象切换状态
public:
virtual void request() = 0; // 状态转移的条件 抽线为函数来触发
}

class A: public state{
void request() const override{
cout << "当前状态A 触发了条件,转化为状态B" << endl;
this->context->set_state(new B); // 当状态为 A 状态时,并且出发了 条件,就切换为B状态,即将上下文中的状态改为B状态 后面再触发该条件,调用的就是 B 状态重写的触发条件函数。
}
}

class B: public state{
void request() const override{
cout << "当前状态B 触发了条件,转化为状态A" << endl;
this->context->set_state(new A);
}
}

class Context{
public:
state* now_state;
void set_state(state* new_st){
if(now_state != nullptr)
delete now_state;
now_state = new_st;
}
void request(){
now_state->request();
}
}

int main(){
Context *ct = Context();
ct->set_state(new A); //设置初始状态

ct->request(); // 状态切换
ct->request(); // 状态切换
}

策略模式

概述

定义一系列算法,封装每个算法,并使它们可以互换。

结构

  • Strategy 接口定义了一个算法族,它们都实现了 behavior() 方法。
  • Context 是使用到该算法族的类,其中的 doSomething() 方法会调用 behavior(),setStrategy(Strategy) 方法可以动态地改变 strategy 对象,也就是说能动态地改变 Context 所使用的算法。

与状态模式的比较

状态模式主要是在运行过程中 根据 触发条件来自动的 更改状态,从而改变 Context对象的行为。而策略模式主要是用来封装以组可以相互替代的算法簇并且可以根据需要动态地去替换 Context 使用地算法。他们实现上类似,都是通过Contex对象中地算法簇指针指向不同地对象,来实现它调用的算法的动态改变,但是切换的时机不一样。

模板方法

概述

定义算法框架,并将一些步骤的实现延迟到子类。通过模板方法,子类可以重新定义算法的某些步骤,而不用改变算法的结构。 例如:对于一个目标跟踪任务,处理流程是 读图像-> 图像预处理 -> 钩子函数-> 跟踪 -> 输出;此时如果我要换中间的 图像预处理的算法,那么我在子类实现的时候重写 图像预处理函数即可。这样可以使得算法结构不变,且可以方便地复用子过程。

结构

就是将算法流程抽象为几个分开步骤,写成虚函数

访问者模式

概述

为一个对象结构(比如组合结构)增加新的功能。一个对象中包含了很多类型的子对象,例如 公司结构中每个员工是一个节点。访问者模式提供了访问对象结构中每个节点的方法。

结构

将访问者 用一个单独的类 Visitor 实现,为每个 对象结构中的每个节点提供 的访问实现一个 visit (ElementA* elm) 接口。在每个节点结构中实现一个 accept (Visitor* ) 函数,并在函数中 调用 Visitor 的 visit 函数,以自身作为参数传入给 Visitor 访问。这样就实现了 Visitor 访问节点的功能。

为何要这样?参考链接

首先这里使用了 函数重载的功能,如果节点对象的类型有很多种,例如 3个节点对象A 3个节点对象B。那么在对每个节点对象进行遍历的时候势必要写一个判断语句,if( it is ElementA ) … else if( it is ElementB) … 当种类多的时候就很麻烦。而我们在 Visitor中的 visit 函数重载了不同类型节点的访问函数,就避免了写判断语句。

image-20210525205621893