軟件系統在接受到指令后,通常需要執(zhí)行各種各樣的操作。例如文本處理軟件的用戶通過用戶界面發(fā)出各種指令,他們想打開一個文檔,保存一個文檔,打印一個文檔,復制一段文本,粘貼一段復制的文本等。這種通用的模式在其他的領域也存在,例如 ,在金融領域中,客戶可以向證券交易商發(fā)出購買股票、出售股票等請求。在制造業(yè)這樣的技術領域,命令被用來控制工業(yè)設備和機器。
在實現由命令控制的軟件系統時,重要的是保證操作的請求者與實際執(zhí)行操作的對象分離。這背后的指導原則是松耦合原則和關注點分離的原則。
餐館就是一個很好的類比。在餐館中,服務員接受顧客點的菜,但服務員不負責做飯,做飯是廚房的事情。事實上,對于顧客來說,食物的制作過程是透明的,也許是餐廳準備食物,也許是食物從其他地方運送過來。
在面向對象的軟件開發(fā)中,由一種名為Command(Action)的行為模式可以促進這種分離。其任務說明如下:
將請求封裝為對象,從而允許你使用不同的請求、隊列或日志的請求參數化客戶端,或支持可撤銷操作。
命令模式的一個很好的例子是Clinet/Server架構體系,其中Client(即所謂的調用者)發(fā)送命令給Server,Server(即所謂的接收者或被調用者)接受并執(zhí)行命令。
讓我們從一個抽象的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"
//命令接收者
// 需要接受并執(zhí)行命令的元素,在這個設計模式中,這個元素被稱為Receiver,在
// 我們的例子中,扮演這個角色的是一個名為Server的類。
// 目前,該類只包含一個可以接受和執(zhí)行命令的簡單公共成員函數。
class Server
{
public:
void acceptCommand(const CommandPtr& command)
{
command->execute();
}
};
#pragma once
// 最后,我們需要所謂的Invoker, 即在Client/Server架構中的Client類;
// 給Server 發(fā)送命令的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;
}
編譯執(zhí)行這個程序,在標準輸出控制臺就會輸出“Hello World!”字符串。通過Command模式實現的是,命令的初始化和發(fā)送與命令的執(zhí)行是分離的。
由于這種設計模式支持開放—封閉(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 發(fā)送命令的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類,因為它可以立即處理和執(zhí)行新擴展的命令。
Command 模式提供了應用程序的多種可能性。例如,可以排隊,也支持命令的異步執(zhí)行:Invoker發(fā)送命令然后立即執(zhí)行其他的操作,發(fā)送的命令稍后由Receiver執(zhí)行。
然而,缺少了一些東西!在上面引用的Command模式的任務聲明中,可以讀到一些關于“……支持可撤銷操作”的內容。
在上一節(jié)的 Client/Server 體系結構的示例中,實際上,服務器不會像上面演示的那樣執(zhí)行命令,到達服務器的命令對象將被分布到負責執(zhí)行命令的服務器的內部。例如,可以在另一種稱為 職責鏈的設計模式的幫助下完成。
考慮一個稍微復雜一點的例子,假設我們有一個繪圖程序,用戶可以用該程序繪制許多不同的形狀,例如,圓形和矩形。為此,可以調用用戶界面相應的菜單進行操作。即:熟悉的軟件開發(fā)人員通過Command設計模式執(zhí)行這些繪圖操作。然而,利益相關者指出用戶也可以撤銷繪圖操作。
為了滿足這個需求,首先我們需要有可撤銷的命令。
//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;
};
很容易想象得出來,繪制矩形和其他形狀得命令和繪制圓形得命令看起來非常相似。命令得執(zhí)行者是一個名為DrawingProcessor的類,這指執(zhí)行繪圖操作的元素,在構造命令對象時,會將該對象的引用與其他參數一起傳遞給構造函數。
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(后進先出)的抽象的數據類型。執(zhí)行了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模式比較合適。
你是否還在尋找穩(wěn)定的海外服務器提供商?創(chuàng)新互聯www.cdcxhl.cn海外機房具備T級流量清洗系統配攻擊溯源,準確流量調度確保服務器高可用性,企業(yè)級服務器適合批量采購,新人活動首月15元起,快前往官網查看詳情吧
名稱欄目:【設計模式——Command模式】-創(chuàng)新互聯
網址分享:http://jinyejixie.com/article20/deccjo.html
成都網站建設公司_創(chuàng)新互聯,為您提供響應式網站、網頁設計公司、域名注冊、微信小程序、外貿建站、搜索引擎優(yōu)化
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯