软件系统在接受到指令后,通常需要执行各种各样的操作。例如文本处理软件的用户通过用户界面发出各种指令,他们想打开一个文档,保存一个文档,打印一个文档,复制一段文本,粘贴一段复制的文本等。这种通用的模式在其他的领域也存在,例如 ,在金融领域中,客户可以向证券交易商发出购买股票、出售股票等请求。在制造业这样的技术领域,命令被用来控制工业设备和机器。
在实现由命令控制的软件系统时,重要的是保证操作的请求者与实际执行操作的对象分离。这背后的指导原则是松耦合原则和关注点分离的原则。
餐馆就是一个很好的类比。在餐馆中,服务员接受顾客点的菜,但服务员不负责做饭,做饭是厨房的事情。事实上,对于顾客来说,食物的制作过程是透明的,也许是餐厅准备食物,也许是食物从其他地方运送过来。
在面向对象的软件开发中,由一种名为Command(Action)的行为模式可以促进这种分离。其任务说明如下:
将请求封装为对象,从而允许你使用不同的请求、队列或日志的请求参数化客户端,或支持可撤销操作。
命令模式的一个很好的例子是Clinet/Server架构体系,其中Client(即所谓的调用者)发送命令给Server,Server(即所谓的接收者或被调用者)接受并执行命令。
让我们从一个抽象的Command类开始,它是一个简单的小接口:
#pragma once
#include
//一个抽象的Command类,它是一个简单的小接口
class Command
{
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
//为指向命令的智能指针引入了一个类型别名(CommandPtr)
using CommandPtr = std::shared_ptr;
// 这个抽象的Command接口可以由各种具体的命令实现,
#pragma once
#include"Command.h"
#include
// 一个非常简单的具体的命令的实现
// 抽象的Command接口可以由各种具体的命令实现,先看一个简单的命令——输出字符串“Hello World!”
class HelloWorldOutputCommand :public Command
{
virtual void execute() override
{
std::cout<< “Hello World\n”<< std::endl;
}
};
#pragma once
#include"Command.h"
//命令接收者
// 需要接受并执行命令的元素,在这个设计模式中,这个元素被称为Receiver,在
// 我们的例子中,扮演这个角色的是一个名为Server的类。
// 目前,该类只包含一个可以接受和执行命令的简单公共成员函数。
class Server
{
public:
void acceptCommand(const CommandPtr& command)
{
command->execute();
}
};
#pragma once
// 最后,我们需要所谓的Invoker, 即在Client/Server架构中的Client类;
// 给Server 发送命令的Client类
#include"Server.h"
#include"HelloWorldOutputCommand.h"
class Client
{
public:
void run()
{
Server theServer{};
CommandPtr helloWorldOutputCommand = std::make_shared();
theServer.acceptCommand(helloWorldOutputCommand);
}
};
#include"Client.h"
// main() 函数
int main()
{
Client client{};
client.run();
return 0;
}
编译执行这个程序,在标准输出控制台就会输出“Hello World!”字符串。通过Command模式实现的是,命令的初始化和发送与命令的执行是分离的。
由于这种设计模式支持开放—封闭(OCP)原则,添加新的命令非常容易,只需对现有代码进行微小的修改即可实。例如,如果想强制服务器等待一段时间,可以添加以下代码:
#pragma once
#include"Command.h"
#include
#include
class WaitCommand :public Command
{
public:
explicit WaitCommand(const unsigned int durationInMilliseconds) noexcept:
durationInMillseconds{ durationInMilliseconds }{};
virtual void execute() override
{
std::chrono::milliseconds dur{ durationInMillseconds };
std::this_thread::sleep_for(dur);
}
private:
unsigned int durationInMillseconds{ 1000 };
};
现在,我们可以像下面这样使用这个新的WaitCommand类:
#pragma once
// 最后,我们需要所谓的Invoker, 即在Client/Server架构中的Client类;
// 给Server 发送命令的Client类
#include"Server.h"
#include"HelloWorldOutputCommand.h"
#include"WaitCommand.h"
class Client
{
public:
/void run()
{
Server theServer{};
CommandPtr helloWorldOutputCommand = std::make_shared();
theServer.acceptCommand(helloWorldOutputCommand);
}/
void run()
{
Server theServer{};
const unsigned int SERVER_DELAY_TIMESPAN{ 3000 };
CommandPtr waitCommand = std::make_shared(SERVER_DELAY_TIMESPAN);
theServer.acceptCommand(waitCommand);
CommandPtr helloWorldOutputCommand = std::make_shared();
theServer.acceptCommand(helloWorldOutputCommand);
}
};
为了对上述讨论的类结构有一个大致的了解,下图描述了对应的UML类图。
正如在这个示例中看到的,我们可以使用值参数化命令,由于纯虚execute()成员函数的签名是由Command接口指定为无参的,因此参数化是在初始化构造函数的帮助下完成的。此外,我们不需要Server类,因为它可以立即处理和执行新扩展的命令。
Command 模式提供了应用程序的多种可能性。例如,可以排队,也支持命令的异步执行:Invoker发送命令然后立即执行其他的操作,发送的命令稍后由Receiver执行。
然而,缺少了一些东西!在上面引用的Command模式的任务声明中,可以读到一些关于“……支持可撤销操作”的内容。
在上一节的 Client/Server 体系结构的示例中,实际上,服务器不会像上面演示的那样执行命令,到达服务器的命令对象将被分布到负责执行命令的服务器的内部。例如,可以在另一种称为 职责链的设计模式的帮助下完成。
考虑一个稍微复杂一点的例子,假设我们有一个绘图程序,用户可以用该程序绘制许多不同的形状,例如,圆形和矩形。为此,可以调用用户界面相应的菜单进行操作。即:熟悉的软件开发人员通过Command设计模式执行这些绘图操作。然而,利益相关者指出用户也可以撤销绘图操作。
为了满足这个需求,首先我们需要有可撤销的命令。
//UndoableCommand接口通过组合Command和Revertable实现
class Command
{
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
class Revertable
{
public:
virtual ~Revertable() = default;
virtual void undo() = 0;
};
class UndoCommand :public Command, public Revertable{};
using CommandPtr = std::shared_ptr;
根据接口隔离原则,我们添加了另一个支持撤销功能的Revertable接口。UndoableCommand类同时继承现有的Command接口和新增的Revertable接口。
有许多不同的撤销绘图的命令,将画圆作为具体的例子:
一个可以撤销画圆的命令
#include"Command.h"
#include"DrawingProcessor.h"
#include"Point.h"
// 一个可以撤销画圆的命令
class DrawingCircleCommand :public UndoablesCommand
{
public:
DrawingCircleCommand(DrawingProcessor& receiver,const Point& centerPoint ,const double radius)noexcept:
receiver{ receiver }, centerPoint{ centerPoint }, radius{ radius }{}
virtual void execute() override {
receiver.drawCircle(centerPoint, radius);
}
virtual void undo() override {
receiver.eraseCircle(centerPoint, radius);
}
private:
DrawingProcessor& receiver;
const Point centerPoint;
const double radius;
};
很容易想象得出来,绘制矩形和其他形状得命令和绘制圆形得命令看起来非常相似。命令得执行者是一个名为DrawingProcessor的类,这指执行绘图操作的元素,在构造命令对象时,会将该对象的引用与其他参数一起传递给构造函数。
DrawingProcessor类是处理绘图操作的元素
#pragma once
#include"Point.h"
// DrawomgProcessor类是处理绘图操作的元素
class DrawingProcessor
{
public:
void drawCircle(const Point& centerPoint, const double radius)
{
// instructions to draw a circle on the screen…
}
void eraseCircle(const Point& centerPoint, const double radius)
{
// Instructions to erase a circle from the screen…
}
};
现在来看这个模式的核心部分CommandProcessor:
#pragma once
#include
#include"Command.h"
//CommandProcessor管理可撤销命令对象的一个堆栈
class CommandProcessor
{
public:
void execute(const CommandPtr& command)
{
command->execute();
commandHistory.push(command);
}
void undoLastCommand()
{
if (commandHistory.empty())
{
return;
}
commandHistory.top()->undo();
commandHistory.pop();
}
private:
//std::stack
std::stack< std::shared_ptr>commandHistory;
};
CommandProcessor类(顺便说一下,上面的类不是线程安全的)包含了std::stack(定义在头文件中),它是一种支持LIFO(后进先出)的抽象的数据类型。执行了CommandProcessor::execute()成员函数后,相应的命令会被存储到commandHistory堆栈中,当调用CommandProcessor::undoLastCommand()成员函数时,存在堆栈上的最后一个命令就会被撤销,然后从堆栈顶部删除。
同样,现在可以将撤销操作建模为命令对象,在这种情况下,命令接收者就是CommandProcessor本身:
UndoCommand 类为CommandProcessor提供撤销操作
#pragma once
// UndoCommand 类为CommandProcessor提供撤销操作
#include"Command.h"
#include"CommandProcessor.h"
class UndoCommand :public UndoableCommand
{
public:
explicit UndoCommand(CommandProcessor& receiver) noexcept :
receiver(receiver) {}
virtual void execute() override
{
receiver.undoLastCommand();
}
virtual void undo() override
{
// intentionally left blank, because an undo should not be undone.
}
private:
CommandProcessor& receiver;
};
在实际使用Command模式时,常常需要能够从几个简单的命令组合成一个更复杂的命令,或者记录和回放命令(脚本)。为了能够方便地实现这些需求,下面的Composite模式比较合适。
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧