【设计模式】职责链与命令模式

本文最后更新于:2023年12月17日 下午

Chain of Responsibility(职责链)——对象行为模式

1. 意图

让多个对象都有机会处理请求,从而避免请求发送者与接收者耦合在一起。将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

2. 动机

参考:责任链(Chain of Responsibility)模式_饭团小神的博客-CSDN博客_责任链

从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确知道哪一个对象将会处理它——我们说该读请求有一个隐式的接受者(implicit receiver)。[1]

职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。

3. 适用性

在以下条件下使用 Chain of Responsibility 模式:

  • 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。

  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。

  • 可处理一个请求的对象集合应该被动态指定。

4. 结构

结构图

一个典型的对象结果可能如下图所示:

5. 参与者

  • Handler
    • 定义一个处理请求的接口
    • 当作为所有ConcreteHandler的接口时,需要设置后续链的接口,当作为责任链最后的结尾可以不用设置后续接口
  • ConcreteHandler
    • 处理它所负责的请求。
    • 可访问它的后继者
    • 如果可处理该请求,就处理这个请求,否则将该请求转发给它的后继者。
  • Client
    • 向链上的具体处理者(ConcreteHandler)对象提交请求。

6. 协作

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

7. 效果

  • 优点:
    • 降低耦合度。该模式使得一个对象无需知道是其他哪一个对象的其请求。对象仅需知道该请求会被“正确”地处理。接受者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。结果是,责任链可以简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需要保持它所有的候选接受者的引用。
    • 增强了给对象指派责任(Responsibility)的灵活性。当在对象中分配职责时,职责链给你更多的灵活性,你可以通过在运行时刻对该链进行动态的增加或者修改来增加或者改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
  • 缺点:
    • 不保证被接受。 既然一个请求没有明确的接受者,那么就不能保证它一定会被处理——该请求可能一直在链对的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。

8. 实现

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

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

    • a) 定义新的链接(通常在Handler中定义,也可由ConcreteHandlers来定义)。
    • b) 使用已有的链接。
  • 连接后继者。 如果没有已有的引用可定义一个链,那么你必须自己引入它们。这种情况下Handler不仅定义该请求的接口,通常也维护后继链接(可以通过构造器实现)。

  • 表示请求。可以有不同的方法表示请求。最简单的形式,请求是一个硬编码的(Hard-coded)操作调用。这种形式方便而且安全。但你只能转发Handler类定义的固定的一组请求。另一种选择是使用一个处理函数,这个函数通常以一个请求码(如一个整型常数或者一个字符串)为参数。这种方法支持请求数目不限。唯一的要求是发送方和接受方在请求如何编码问题上达成一致。

9. 示例代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// ChainResponsibility.h
// Created by 张宇轩 on 2022/5/8.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <string>

using namespace std;
#ifndef BEHAVIORALPATTERNDESIGN_CHAINRESPONSIBILITY_H
#define BEHAVIORALPATTERNDESIGN_CHAINRESPONSIBILITY_H

/**
* @brief 参考这个例子:https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.html
* 模拟场景:把打印的消息作为请求,把消息传递给Logger,各个Logger判断该消息级别是否符合自身要求,
* 如果符合则进行处理打印,否则交给下一级(更高级)的Logger
*/

void test_chain_responsibility();

enum class MESSAGE_LEVEL {
INFO,
DEBUG,
WARNING,
ERROR,
};

// 抽象日志,相当于是 Handler类
class AbstractLogger {
protected:
AbstractLogger* next_logger;
MESSAGE_LEVEL level;

public:
void set_next_logger(AbstractLogger* logger) {
if (logger != nullptr)
next_logger = logger;
}
AbstractLogger(MESSAGE_LEVEL level, AbstractLogger* logger): level(level), next_logger(logger) {

}
virtual void print_message(MESSAGE_LEVEL info_level, string message) {
if (level >= info_level) {
cout << message << endl;
} else if (next_logger != nullptr){
next_logger->print_message(info_level,message);
}
}
};

class InfoLogger : public AbstractLogger {
public:
InfoLogger(MESSAGE_LEVEL level, AbstractLogger* logger): AbstractLogger(level, logger) {
}
virtual void print_message(MESSAGE_LEVEL info_level, string message) {
if (level >= info_level) {
cout << "InfoLogger 输出消息颜色为黑色,消息:" << message << endl;
} else if (next_logger != nullptr) {
next_logger->print_message(info_level,message);
}
}
};
class DebugLogger: public AbstractLogger {
public:
DebugLogger(MESSAGE_LEVEL level, AbstractLogger* logger): AbstractLogger(level, logger) {
}
virtual void print_message(MESSAGE_LEVEL info_level, string message) {
if (level >= info_level) {
cout << "DebugLogger 输出消息颜色为灰色,消息:" << message << endl;
} else if (next_logger != nullptr){
next_logger->print_message(info_level,message);
}
}
};
class WarningLogger : public AbstractLogger {
public:
WarningLogger(MESSAGE_LEVEL level, AbstractLogger* logger): AbstractLogger(level, logger) {
}
virtual void print_message(MESSAGE_LEVEL info_level, string message) {
if (level >= info_level) {
cout << "WarningLogger 输出消息颜色为黄色,消息:" << message << endl;
} else if (next_logger != nullptr){
next_logger->print_message(info_level,message);
}
}
};
class ErrorLogger : public AbstractLogger {
public:
ErrorLogger(MESSAGE_LEVEL level, AbstractLogger* logger): AbstractLogger(level, logger) {
}
virtual void print_message(MESSAGE_LEVEL info_level, string message) {
if (level >= info_level) {
cout << "ErrorLogger 输出消息颜色为红色,消息:" << message << endl;
} else if (next_logger != nullptr){
next_logger->print_message(info_level,message);
}
}
};

#endif //BEHAVIORALPATTERNDESIGN_CHAINRESPONSIBILITY_H
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
// ChainResponsibility.cpp
// Created by 张宇轩 on 2022/5/8.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "ChainResponsibility.h"
AbstractLogger* get_logger() {
AbstractLogger* info_logger = new InfoLogger(MESSAGE_LEVEL::INFO, nullptr);
AbstractLogger* debug_logger = new DebugLogger(MESSAGE_LEVEL::DEBUG, nullptr);
AbstractLogger* warning_logger = new WarningLogger(MESSAGE_LEVEL::WARNING, nullptr);
AbstractLogger* error_logger = new ErrorLogger(MESSAGE_LEVEL::ERROR, nullptr);
info_logger->set_next_logger(debug_logger);
debug_logger->set_next_logger(warning_logger);
warning_logger->set_next_logger(error_logger);
return info_logger;
}

void test_chain_responsibility() {
cout << "——————开始测试 Chain of Responsibiliy 模式——————" << endl;

AbstractLogger* logger = get_logger();
logger->print_message(MESSAGE_LEVEL::DEBUG, "调试当前数据");
logger->print_message(MESSAGE_LEVEL::INFO, "输出一条普通信息");
logger->print_message(MESSAGE_LEVEL::ERROR, "运行时报错!");
logger->print_message(MESSAGE_LEVEL::WARNING, "有一个变量没有赋初值!");

cout << "——————结束测试 Chain of Responsibiliy 模式——————" << endl;

}

10. 相关模式

Composite模式: 责任链模式长与Composite模式一起使用。这种情况下,一个构件的父构件可作为它的后续。

Command(命令)——对象行为型模式

1. 意图

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

2. 别名

动作(action),事物(transaction)

3. 动机

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接受者的任何信息。

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

4. 适用性

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

  • 需要抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(callback)函数表达式这种参数化机制。所谓的回调函数是指某函数先在某处注册,而它将在稍后某个需要的时候被调用。Command模式时回调机制的一个面向对象的代替品。
  • 在不同的时刻指定、排列和执行请求。一个Command对象可以有一个与初始请求无关的生存期。
  • 支持取消操作 Command的Execute操作,可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command接口必须添加一个Unexecute操作,该操作取消上一次Execute调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这个列表并分别调用undo和execute来实现重数不限的“撤销”和“执行”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍 在Command接口中添加中添加装载操作和存储操作,可以用来保持变动的一个一致的修改日志。
  • 用来构建在原语操作上的高层操作构造一个系统。这样一种的结构在支持**事务(transaction)**的信息系统中很常见。

5. 结构

结构图

6. 参与者

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

7. 协作

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

下图是展示了这些对象之间的交互的序列图。它说明了Command是如何将调用者和接受者(以及它执行的请求)解耦的:[2]

图片来源:https://blog.csdn.net/qq_38636211/article/details/97755767?spm=1001.2014.3001.5502

8. 效果

Command模式有以下的优点和缺点:

优点

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

缺点

  • 类的数目大大增加 完成一个目标需要大量的类和对象协同合作。应用程序开发人员需要小心正确地开发这些类。
  • 提升了实现和维护的工作量 每个单独的命令都是一个ConcreteCommand类,它增加了实现和维护的类的数量。

9. 实现

实现Command模式需要考虑一下问题:

  • 一个命令对象应该达到何种的智能程度。 命令对象的能力可大可小。一个极端是它仅确定一个接受者和执行该请求的动作。另一极端是它自己实现了所有功能。
  • **支持取消(undo)和重做(redo)**。如果Command提供方法逆转(reverse)它们操作的执行(例如LightOnCommand的undo()),就可以支持取消和重做(两次撤销就相当于重做啊! 仅仅只存储一次操作时)功能。为达到这个目的ConcreteCommand类可能需要存储额外的状态信息。
  • 避免取消操作过程中的错误累积
  • 使用C++模板。 对于不能撤销和不需要参数的命令,可使用C++模板来实现,这样可以避免为每一种动作和接收者都创建一个Command子类。

10. 示例代码

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Command.h
// Created by 张宇轩 on 2022/5/8.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

using namespace std;
#ifndef BEHAVIORALPATTERNDESIGN_COMMAND_H
#define BEHAVIORALPATTERNDESIGN_COMMAND_H

void test_command();

/**
* @brief 参考:http://c.biancheng.net/view/1380.html
* 模拟场景:一个餐馆可以对提供多个功能(抽象成命令),而这些功能/命令具体由特别的人物负责,
* 例如饭菜由厨师负责,收拾由服务员负责,收银由收银员负责
*/

// 厨师,相当于 Receiver 类
class Chief {
public:
void cook() {
cout << "大厨正在烧菜..." << endl;
cout << "菜烧好了!" << endl;
}
};

// 服务员,相当于 Receiver 类
class Waiter {
public:
void clean_table() {
cout << "服务员正在清理桌面..." << endl;
cout << "桌面理好了!" << endl;
}

void serve_meal() {
cout << "服务员正在上菜..." << endl;
}

};

// 收银员,相当于 Receiver 类
class Cashier {
public:
void checkout() {
cout << "收银员结账" << endl;
}
};

// 服务,相当于抽象出来的 Command类
class Service {
public:
virtual void serve() = 0;
};

// 提供餐食,这是一个组合命令,厨师做菜,然后服务员端菜
class ProvideMeal : public Service {
private:
Chief *chief;
Waiter *waiter;
public:
ProvideMeal(Chief *chief, Waiter *waiter) : chief(chief), waiter(waiter) {

}

void serve() {
chief->cook();
waiter->serve_meal();
}
};

// 收拾碗筷
class CleanUp : public Service {
private:
Waiter *waiter;
public:
CleanUp(Waiter *waiter) : waiter(waiter) {

}

void serve() {
waiter->clean_table();
}
};

// 收银
class Cashiering : public Service {
private:
Cashier *cashier;
public:
Cashiering(Cashier *cashier) : cashier(cashier) {

}

void serve() {
cashier->checkout();
}
};

// 餐馆,相当于 Invoker类
class Restaurant {
private:
Service *meal;
Service *clean;
Service *cash;
public:
Restaurant(Service *meal, Service *clean, Service *cash) : meal(meal), clean(clean), cash(cash) {

}

void serve_client() {
cout << "食客进入餐馆..." << endl;
meal->serve();


cout << "\n食客用食完毕..." << endl;
cash->serve();

cout << "\n食客离开餐馆..." << endl;
clean->serve();
}

};
#endif //BEHAVIORALPATTERNDESIGN_COMMAND_H
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Command.cpp
// Created by 张宇轩 on 2022/5/8.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include "Command.h"
void test_command() {
cout << "——————开始测试 Command 命令模式——————" << endl;

Service *meal = new ProvideMeal(new Chief, new Waiter);
Service *clean = new CleanUp(new Waiter);
Service *cash = new Cashiering(new Cashier);

Restaurant restaurant(meal, clean, cash);
restaurant.serve_client();

cout << "——————开始测试 Command 命令模式——————" << endl;

}

11. 相关模式

  • Composite(组合)模式: 可以使用组合模式来实现宏命令

  • Memento(备忘录)模式: 可用来保持某个状态,命令用这一状态来取消它的效果。

  • Proxytype(原型)模式: 在被放入历史列表前必须被拷贝的命令可以起到一种原型的作用

备注/参考


【设计模式】职责链与命令模式
https://2017zhangyuxuan.github.io/2022/05/09/2022-05/2022-05-09 【设计模式】职责链与命令模式/
作者
Zhang Yuxuan
发布于
2022年5月9日
许可协议