【设计模式——Command模式】

Command模式

软件系统在接受到指令后,通常需要执行各种各样的操作。例如文本处理软件的用户通过用户界面发出各种指令,他们想打开一个文档,保存一个文档,打印一个文档,复制一段文本,粘贴一段复制的文本等。这种通用的模式在其他的领域也存在,例如 ,在金融领域中,客户可以向证券交易商发出购买股票、出售股票等请求。在制造业这样的技术领域,命令被用来控制工业设备和机器。
在实现由命令控制的软件系统时,重要的是保证操作的请求者与实际执行操作的对象分离。这背后的指导原则是松耦合原则和关注点分离的原则。
餐馆就是一个很好的类比。在餐馆中,服务员接受顾客点的菜,但服务员不负责做饭,做饭是厨房的事情。事实上,对于顾客来说,食物的制作过程是透明的,也许是餐厅准备食物,也许是食物从其他地方运送过来。
在面向对象的软件开发中,由一种名为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<HelloWorldOutputCommand>();
	theServer.acceptCommand(helloWorldOutputCommand);
}

};
为了对上述讨论的类结构有一个大致的了解,下图描述了对应的UML类图。
Server类仅知道Command接口,不知道任何具体的命令类
正如在这个示例中看到的,我们可以使用值参数化命令,由于纯虚execute()成员函数的签名是由Command接口指定为无参的,因此参数化是在初始化构造函数的帮助下完成的。此外,我们不需要Server类,因为它可以立即处理和执行新扩展的命令。
Command 模式提供了应用程序的多种可能性。例如,可以排队,也支持命令的异步执行:Invoker发送命令然后立即执行其他的操作,发送的命令稍后由Receiver执行。
然而,缺少了一些东西!在上面引用的Command模式的任务声明中,可以读到一些关于“……支持可撤销操作”的内容。

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::shared_ptr> commandHistory;
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模式比较合适。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
video模式command模式是指在计算机领域中两种不同的操作界面或交互方式。 1. Video模式(图形用户界面):Video模式是指使用图形用户界面(GUI)进行操作的模式。在这种模式下,计算机系统以图像、图标和视觉效果的形式来呈现信息和操作选项。用户可以使用鼠标、键盘或触摸屏等输入设备与计算机进行交互。在图形用户界面中,通常有窗口、菜单、按钮等元素,用户可以通过直观的操作进行任务的执行,例如拖拽、点击等。常见的操作系统如Windows、macOS和Linux都提供了图形用户界面。 2. Command模式命令行界面):Command模式是指使用命令行界面(CLI)进行操作的模式。在这种模式下,用户通过输入命令和参数来与计算机进行交互,计算机执行用户输入的命令并返回结果。用户通过键盘输入命令命令行界面通常以文本方式显示信息和操作选项。在命令行界面中,用户需要记住命令和其对应的参数,并按照特定的语法进行输入。在操作系统中,例如Windows提供的命令提示符(Command Prompt)和Linux提供的终端(Terminal)就是常见的命令行界面。 视频模式命令模式在不同场景中有各自的优势和用途。视频模式通常更适合图形化的任务和操作,提供了直观的交互方式,适合初学者和普通用户使用。而命令模式则更适合需要精确控制和批量处理的任务,也更适用于高级用户和开发人员。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值