介绍
命令模式将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为模式,其别名为动作模式或事务模式。
实现
myclass.h
//
// Created by yuwp on 2024/1/12.
//
#ifndef DESIGNPATTERNS_MYCLASS_H
#define DESIGNPATTERNS_MYCLASS_H
#include <iostream>
#include <unordered_map>
#include <atomic>
class Command { // 命令抽象类
public:
virtual void execute() = 0;
};
class Reciever { // 命令接收者,可以继承实现具体的接收类
public:
virtual void action();
};
class ConcreteCommand : public Command { // 具体命令类
public:
ConcreteCommand();
~ConcreteCommand();
void execute() override;
private:
Reciever *m_reciever;
};
class Invoker { // 调用者类,命令发送者
public:
Invoker(Command *command);
void call();
private:
Command *m_command;
};
#endif //DESIGNPATTERNS_MYCLASS_H
myclass.cpp
//
// Created by yuwp on 2024/1/12.
//
#include "myclass.h"
#include <thread>
#include <unistd.h>
ConcreteCommand::ConcreteCommand() {
m_reciever = new Reciever();
}
ConcreteCommand::~ConcreteCommand() {
if (m_reciever)
delete m_reciever;
}
void ConcreteCommand::execute() {
m_reciever->action();
}
void Reciever::action() {
std::cout << "接收者处理命令" << std::endl;
}
Invoker::Invoker(Command *command) {
m_command = command;
}
void Invoker::call() {
m_command->execute();
}
main.cpp
#include <iostream>
#include <mutex>
#include "myclass.h"
int main() {
Command *command = new ConcreteCommand();
Invoker *invoker = new Invoker(command);
invoker->call();
return 0;
}
命令队列实现
只需要增加一个CommandQueue类即可,Invoker中保存对CommandQueue的引用。
myclass.h
//
// Created by yuwp on 2024/1/12.
//
#ifndef DESIGNPATTERNS_MYCLASS_H
#define DESIGNPATTERNS_MYCLASS_H
#include <iostream>
#include <unordered_map>
#include <atomic>
#include <vector>
class Command { // 命令抽象类
public:
virtual void execute() = 0;
};
class Reciever { // 命令接收者,可以继承实现具体的接收类
public:
virtual void action();
};
class ConcreteCommand : public Command { // 具体命令类
public:
ConcreteCommand();
~ConcreteCommand();
void execute() override;
private:
Reciever *m_reciever;
};
class CommandQueue {
public:
void execute();
void addCommand(Command *command);
void removeCommand(Command *command);
private:
std::vector<Command *> m_commands;
};
class Invoker { // 调用者类,命令发送者
public:
Invoker(CommandQueue *commands);
void call();
private:
CommandQueue *m_commands;
};
#endif //DESIGNPATTERNS_MYCLASS_H
myclass.cpp
//
// Created by yuwp on 2024/1/12.
//
#include "myclass.h"
#include <thread>
#include <unistd.h>
ConcreteCommand::ConcreteCommand() {
m_reciever = new Reciever();
}
ConcreteCommand::~ConcreteCommand() {
if (m_reciever)
delete m_reciever;
}
void ConcreteCommand::execute() {
m_reciever->action();
}
void Reciever::action() {
std::cout << "接收者处理命令" << std::endl;
}
Invoker::Invoker(CommandQueue *commands) {
m_commands = commands;
}
void Invoker::call() {
m_commands->execute();
}
void CommandQueue::execute() {
for (auto it = m_commands.begin(); it != m_commands.end(); ++it) {
(*it)->execute();
}
}
void CommandQueue::addCommand(Command *command) {
m_commands.push_back(command);
}
void CommandQueue::removeCommand(Command *command) {
for (auto it = m_commands.begin(); it != m_commands.end(); ) {
if (*it == command) {
it = m_commands.erase(it);
} else {
++it;
}
}
}
main.cpp
#include <iostream>
#include <mutex>
#include "myclass.h"
int main() {
Command *command = new ConcreteCommand();
CommandQueue *queue = new CommandQueue();
queue->addCommand(command);
Invoker *invoker = new Invoker(queue);
invoker->call();
delete invoker;
delete queue;
delete command;
return 0;
}
撤销操作
在命令类中增加一个逆向操作,也可以通过保存对象的历史状态来实现状态回滚。
请求日志
执行命令时增加日志的记录操作。
宏命令
组合模式和命令模式的结合,可以实现批量命令的执行,与命令队列类似,不同的是宏命令类继承自命令抽象类(Command),可以递归调用。
总结
优点
1. 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者。同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
2. 新的命令可以很容易地加入系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码甚至客户类代码,满足开闭原则的要求。
3. 可以比较容易地设计一个命令队列或宏命令(组合命令)。
4. 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
缺点
1. 使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
适用场景
1. 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
2. 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期。换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
3. 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4. 系统需要将一组操作组合在一起形成宏命令。
练习
myclass.h
//
// Created by yuwp on 2024/1/12.
//
#ifndef DESIGNPATTERNS_MYCLASS_H
#define DESIGNPATTERNS_MYCLASS_H
#include <iostream>
#include <unordered_map>
#include <atomic>
#include <vector>
class Command { // 命令抽象类
public:
virtual void execute() = 0;
};
class BoardScreen { // 命令接收者
public:
void open();
void create();
void edit();
};
class OpenCommand : public Command { // 具体命令类
public:
OpenCommand(BoardScreen *reciever);
void execute() override;
private:
BoardScreen *m_reciever;
};
class CreateCommand : public Command { // 具体命令类
public:
CreateCommand(BoardScreen *reciever);
void execute() override;
private:
BoardScreen *m_reciever;
};
class EditCommand : public Command { // 具体命令类
public:
EditCommand(BoardScreen *reciever);
void execute() override;
private:
BoardScreen *m_reciever;
};
class Invoker { // 调用者类,命令发送者
public:
Invoker(Command *command);
void call();
private:
Command *m_command;
};
#endif //DESIGNPATTERNS_MYCLASS_H
myclass.cpp
//
// Created by yuwp on 2024/1/12.
//
#include "myclass.h"
#include <thread>
#include <unistd.h>
void BoardScreen::open() {
std::cout << "打开" << std::endl;
}
void BoardScreen::create() {
std::cout << "新建" << std::endl;
}
void BoardScreen::edit() {
std::cout << "编辑" << std::endl;
}
OpenCommand::OpenCommand(BoardScreen *reciever) {
m_reciever = reciever;
}
void OpenCommand::execute() {
m_reciever->open();
}
CreateCommand::CreateCommand(BoardScreen *reciever) {
m_reciever = reciever;
}
void CreateCommand::execute() {
m_reciever->create();
}
EditCommand::EditCommand(BoardScreen *reciever) {
m_reciever = reciever;
}
void EditCommand::execute() {
m_reciever->edit();
}
Invoker::Invoker(Command *command) {
m_command = command;
}
void Invoker::call() {
m_command->execute();
}
main.cpp
#include <iostream>
#include <mutex>
#include "myclass.h"
int main() {
BoardScreen *boardScreen = new BoardScreen();
Command *command = new OpenCommand(boardScreen);
Invoker *invoker = new Invoker(command);
invoker->call();
delete command;
delete invoker;
command = new CreateCommand(boardScreen);
invoker = new Invoker(command);
invoker->call();
delete command;
delete invoker;
command = new EditCommand(boardScreen);
invoker = new Invoker(command);
invoker->call();
delete command;
delete invoker;
return 0;
}