引入:以一个对数组的增删改查为例。通过命令模式可以对数组进行增删改查以及撤销回滚。
一、基本概念
命令模式有多种分法,在本文中主要分为CommandMgr、Command、Receiver.
CommandMgr主要用于控制命令执行等操作、Command为具体的命令、Receiver为命令具体要操作的对象。
总而言之,增删改查就是具体的Command、Receiver就是数组、CommandMgr负责控制命令的执行与回滚等。
二、程序设计
以下代码可从github下载:GitHub - laizilianglaiziliang/LearnCommandProject
1.Receiver
cpp
//"Receiver_Array.h"
#pragma once
#include<vector>
#include<optional>
#include<iostream>
template <class T>
class Receiver_Array
{
private:
std::vector<T>* myArry;
public:
~Receiver_Array()
{
}
Receiver_Array()
{
myArry = new std::vector<T>();
}
Receiver_Array(std::vector<T>* _myArry)
{
if (_myArry)
{
myArry = new std::vector<T>(*_myArry);
}
else
{
myArry = new std::vector<T>();
}
}
bool add(const int pos, const T& val)
{
if (errorCheck(pos))
{
return false;
}
myArry->insert(pos + myArry->begin(), val);
return true;
}
bool del(const int pos)
{
if (errorCheck(pos))
{
return false;
}
myArry->erase(pos + myArry->begin());
return true;
}
bool modify(const int pos, const T& val)
{
if (errorCheck(pos))
{
return false;
}
myArry->erase(pos + myArry->begin());
return true;
}
std::optional<T> seek(const int pos)
{
if (errorCheck(pos))
{
return std::nullopt;
}
return (*myArry)[pos];
}
bool errorCheck(const int pos)
{
if (pos >= myArry->size())
{
printf(" Operation Failed.Array Bounds Errors. ");
return true;
}
return false;
}
void display()
{
for (int i = 0; i < myArry->size(); ++i)
{
std::cout << (*myArry)[i] << " ";
}
std::cout << std::endl;
}
};
在本例子中Receiver_Array类是一个模板类,可支持不同类型的数组。同时实现了对数组进行增删改查,为不同的命令类提供了基础的功能。
2.Command
cpp
//Command.h
#pragma once
#include "Receiver_Array.h"
#include<optional>
#include <memory>
class Command
{
public:
int m_iPos;
bool m_bCanRevert;
public:
Command(int _pos) : m_iPos(_pos), m_bCanRevert(true)
{
}
virtual ~Command(){}
virtual bool execute() = 0;
virtual void* executeOther()
{
return nullptr;
}
virtual bool undo() = 0;
virtual bool redo() = 0;
};
template <class T>
class AddCommand:public Command
{
private:
std::shared_ptr<Receiver_Array<T>> m_Receiver_Array;
T m_Val;
public:
AddCommand() {}
AddCommand(std::shared_ptr<Receiver_Array<T>> _receiver_Array, int _pos,const T& _val) :Command( _pos), m_Receiver_Array(_receiver_Array), m_Val(_val)
{
}
virtual ~AddCommand()
{
//if (m_Receiver_Array)
//{
// m_Receiver_Array->destory();
// m_Receiver_Array = nullptr;
//}
}
bool execute() override
{
try
{
if (this->m_Receiver_Array->add(this->m_iPos, m_Val))
{
printf(" Add Success.");
return true;
}
printf(" Add Fail.");
return false;
}
catch(...)
{
printf(" Add Fail.");
return false;
}
return true;
}
virtual bool undo() override
{
try
{
if (this->m_Receiver_Array->del(this->m_iPos))
{
printf(" Undo Success.");
return true;
}
}
catch (...)
{
}
printf(" Undo Fail.");
return false;
}
virtual bool redo() override
{
if (execute())
{
printf(" Redo Success.");
return true;
}
printf(" Redo Fail.");
return false;
}
};
template <class T>
class DelCommand :public Command
{
private:
std::shared_ptr<Receiver_Array<T>> m_Receiver_Array;
T m_Val;
public:
DelCommand() {}
DelCommand(std::shared_ptr<Receiver_Array<T>> _receiver_Array, int _pos) :Command(_pos), m_Receiver_Array(_receiver_Array)
{
}
virtual ~DelCommand()
{
//if (m_Receiver_Array)
//{
// m_Receiver_Array->destory();
// m_Receiver_Array = nullptr;
//}
}
bool execute() override
{
try
{
std::optional<T> val = m_Receiver_Array->seek(m_iPos);
if (val!=std::nullopt && this->m_Receiver_Array->del(this->m_iPos))
{
printf(" Del Success.");
m_Val = val.value();
return true;
}
printf(" Del Fail.");
return false;
}
catch (...)
{
printf(" Del Fail.");
return false;
}
return true;
}
virtual bool undo() override
{
try
{
if (this->m_Receiver_Array->add(this->m_iPos, m_Val))
{
printf(" Undo Success.");
return true;
}
}
catch (...)
{
}
printf(" Undo Fail.");
return false;
}
virtual bool redo() override
{
if (execute())
{
printf(" Redo Success.");
return true;
}
printf(" Redo Fail.");
return false;
}
};
template <class T>
class ModifyCommand :public Command
{
private:
std::shared_ptr<Receiver_Array<T>> m_Receiver_Array;
T m_Val;
public:
ModifyCommand() {}
ModifyCommand(std::shared_ptr<Receiver_Array<T>> _receiver_Array, int _pos,const T& _val) :Command(_pos), m_Receiver_Array(_receiver_Array), m_Val(_val)
{
}
virtual ~ModifyCommand()
{
//if (m_Receiver_Array)
//{
// m_Receiver_Array->destory();
// m_Receiver_Array = nullptr;
//}
}
bool execute() override
{
try
{
std::optional<T> val = this->m_Receiver_Array->seek(m_iPos);//判断m_iPos是合法的
if (val != std::nullopt && this->m_Receiver_Array->modify(this->m_iPos, m_Val))
{
printf(" Modify Success.");
m_Val = val.has_value();//用于undo redo
return true;
}
printf(" Modify Fail.");
return false;
}
catch (...)
{
printf(" Modify Fail.");
return false;
}
return true;
}
virtual bool undo() override
{
try
{
if (execute())
{
printf(" Undo Success.");
return true;
}
}
catch (...)
{
}
printf(" Undo Fail.");
return false;
}
virtual bool redo() override
{
if (execute())
{
printf(" Redo Success.");
return true;
}
printf(" Redo Fail.");
return false;
}
};
template <class T>
class SeekCommand :public Command
{
private:
std::shared_ptr<Receiver_Array<T>> m_Receiver_Array;
public:
SeekCommand():m_bCanRevert(false) {}
SeekCommand(std::shared_ptr<Receiver_Array<T>> _receiver_Array, int _pos) :Command(_pos), m_Receiver_Array(_receiver_Array)
{
m_bCanRevert = false;
//, m_bCanRevert(false)
}
virtual ~SeekCommand()
{
//if (m_Receiver_Array)
//{
// m_Receiver_Array->destory();
// m_Receiver_Array = nullptr;
//}
}
bool execute() override
{
return false;
}
virtual void* executeOther() override
{
try
{
std::optional<T> val = m_Receiver_Array->seek(m_iPos);
if (val == std::nullopt)
{
printf(" Seek Fail.");
return nullptr;
}
printf(" Seek Success.");
T* ret = new T();
*ret = val.value();
return ret;
}
catch (...)
{
}
printf(" Seek Fail.");
return nullptr;
}
virtual bool undo() override
{
printf(" Undo Fail.");
return false;
}
virtual bool redo() override
{
printf(" Redo Fail.");
return false;
}
};
**1)**Command类是命令基类。本来也想将Command设计成模板类,但是后面想想感觉不太好,因为Command设计成模板类会影响到CommandMgr也变成模板类。如果Command类是模板类,要注意其属性如果在派生类中要用的话要用this指针去访问,否则会出现找不到标识符的问题。
可参考:
C++模板类中,派生类使用基类中数据或方法报"找不到标识符"_c++头文件引用其他类提示找不到符号-CSDN博客
**2)**Command类中有个m_bCanRevert属性用于判断该命令是否可以被撤销回滚,因为并不是所有的命令都支持撤销回滚,比如例子中的SeekCommand。
**3)**Command类中有个executeOther,因为SeekCommand执行后需要返回一个值,是特殊的命令,因此executeOther用于执行特殊的命令
**4)**其他的Command派生类依赖于Receiver_Array类,可能会出现多个类依赖于同一个Receiver_Array类对象的情况,因此把Receiver_Array类成员变量设置为智能指针方便内存的释放
**5)**其他的主要就是实现每个Command类的execute、undo、redo方法,这个直接看逻辑就能理解。
3.CommandMgr
cpp
//CommandMgr.h
#pragma once
#include <stack>
#include <memory>
class Command;
class CommandMgr
{
private:
std::stack<std::shared_ptr<Command>> m_stkUndo;//undo栈
std::stack<std::shared_ptr<Command>> m_stkRedo;//redo栈
public:
CommandMgr();
~CommandMgr();
void execute(std::shared_ptr<Command> command) noexcept;
void* executeOther(std::shared_ptr<Command> command)noexcept;
void undo() noexcept;
void redo() noexcept;
};
cpp
//CommandMgr.cpp
#include "CommandMgr.h"
#include "Command.h"
CommandMgr::CommandMgr()
{
}
CommandMgr::~CommandMgr()
{
while (!m_stkRedo.empty())
{
m_stkRedo.pop();
}
while (!m_stkUndo.empty())
{
m_stkUndo.pop();
}
}
void CommandMgr::execute(std::shared_ptr<Command> command) noexcept
{
if (command->execute())
{
printf(" Command Execute Success\n\n");
if (command->m_bCanRevert)
{
m_stkUndo.push(command);
}
}
else
{
printf(" Command Execute Fail\n\n");
}
}
void* CommandMgr::executeOther(std::shared_ptr<Command> command) noexcept
{
void* val = command->executeOther();
if (val)
{
printf(" Command Execute Success\n\n");
if (command->m_bCanRevert)
{
m_stkUndo.push(command);
}
return val;
}
else
{
printf(" Command Execute Fail\n\n");
}
return nullptr;
}
void CommandMgr::undo() noexcept
{
if (m_stkUndo.empty())
{
return;
}
std::shared_ptr<Command> command = m_stkUndo.top();
if (command && command->m_bCanRevert && command->undo())
{
m_stkUndo.pop();
m_stkRedo.push(command);
printf(" Command Undo Success\n\n");
}
else
{
printf(" Command Undo Fail\n\n");
}
}
void CommandMgr::redo() noexcept
{
if (m_stkRedo.empty())
{
return;
}
std::shared_ptr<Command> command = m_stkRedo.top();
if (command && command->m_bCanRevert && command->redo())
{
m_stkUndo.push(command);
printf(" Command Redo Success\n\n");
}
else
{
printf(" Command Redo Fail\n\n");
}
}
**1)**CommandMgr主要用于管理命令,用来操作具体的命令的调用控制、undo、redo控制。
**2)**因为Command类型对象的内存不太好管理,因此也使用了智能指针。
**3)**里面的undo、redo主要通过栈来实现。当命令execute过后便会添加到undo栈,接下来的undo、redo主要就是对undo栈和redo栈进行互相倒腾。
4.main函数
当做完上面的工作就能对数组进行方便的增删改查了,还可以撤销回退哦。
cpp
// LearnCommandProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include <iostream>
#include <vector>
#include "Command.h"
#include "Receiver_Array.h"
#include "CommandMgr.h"
#include <memory>
int main()
{
std::vector<int> vec{ 1,2,3 };
std::shared_ptr<Receiver_Array<int>> receiver_Array(new Receiver_Array<int>(&vec));
std::shared_ptr<AddCommand<int>> addCommand(new AddCommand<int>(receiver_Array, 3, 4));
CommandMgr commandMgr;
commandMgr.execute(addCommand);
commandMgr.undo();
commandMgr.redo();
receiver_Array->display();
std::shared_ptr<SeekCommand<int>> seekCommand(new SeekCommand<int>(receiver_Array, 1));
int* val= (int*)(commandMgr.executeOther(seekCommand));
receiver_Array->display();
delete val;
val = nullptr;
std::shared_ptr<DelCommand<int>> delCommand(new DelCommand(receiver_Array, 1));
commandMgr.execute(delCommand);
commandMgr.undo();
commandMgr.redo();
receiver_Array->display();
std::shared_ptr<ModifyCommand<int>> modifyCommand(new ModifyCommand(receiver_Array, 2, 2));
commandMgr.execute(modifyCommand);
commandMgr.undo();
commandMgr.redo();
receiver_Array->display();
printf("ok");
}
.上面的代码可能还有设计不好的地方,欢迎批评指正。