目錄
- 前言
- 問題
- 解決方案
- 結構
- 代碼
前言
命令是一種行為設計模式,它可將請求轉換為一個包含與請求相關的所有信息的獨立對象。該轉換讓你能根據不同的請求將方法參數化、延遲請求執行或將其放入隊列中,且能實現可撤銷操作。
問題
假如你正在開發一款新的文字編輯器, 當前的任務是創建一個包含多個按鈕的工具欄, 并讓每個按鈕對應編輯器的不同操作。 你創建了一個非常簡潔的 按鈕 類, 它不僅可用于生成工具欄上的按鈕,還可用于生成各種對話框的通用按鈕。
盡管所有按鈕看上去都很相似, 但它們可以完成不同的操作(打開、保存、打印和應用等)。你會在哪里放置這些按鈕的點擊處理代碼呢? 最簡單的解決方案是在使用按鈕的每個地方都創建大量的子類。 這些子類中包含按鈕點擊后必須執行的代碼。
你很快就意識到這種方式有嚴重缺陷。 首先, 你創建了大量的子類, 當每次修改基類 按鈕 時, 你都有可能需要修改所有子類的代碼。 簡單來說, GUI 代碼以一種拙劣的方式依賴于業務邏輯中的不穩定代碼。
還有一個部分最難辦。復制/粘貼文字等操作可能會在多個地方被調用。例如用戶可以點擊工具欄上小小的“復制”按鈕,或者通過上下文菜單復制一些內容, 又或者直接使用鍵盤上的 Ctrl+C 。
我們的程序最初只有工具欄, 因此可以使用按鈕子類來實現各種不同操作。 換句話來說, 復制按鈕 CopyButton 子類包含復制文字的代碼是可行的。 在實現了上下文菜單、 快捷方式和其他功能后, 你要么需要將操作代碼復制進許多個類中,要么需要讓菜單依賴于按鈕,而后者是更糟糕的選擇。
解決方案
優秀的軟件設計通常會將關注點進行分離, 而這往往會導致軟件的分層。最常見的例子:一層負責用戶圖像界面;另一層負責業務邏輯。 GUI 層負責在屏幕上渲染美觀的圖形, 捕獲所有輸入并顯示用戶和程序工作的結果。 當需要完成一些重要內容時(比如計算月球軌道或撰寫年度報告,GUI 層則會將工作委派給業務邏輯底層。
這在代碼中看上去就像這樣: 一個 GUI 對象傳遞一些參數來調用一個業務邏輯對象。 這個過程通常被描述為一個對象發送請求給另一個對象。
命令模式建議 GUI 對象不直接提交這些請求。 你應該將請求的所有細節(例如調用的對象、 方法名稱和參數列表)抽取出來組成命令類,該類中僅包含一個用于觸發請求的方法。
命令對象負責連接不同的 GUI 和業務邏輯對象。 此后, GUI對象無需了解業務邏輯對象是否獲得了請求, 也無需了解其對請求進行處理的方式。 GUI 對象觸發命令即可, 命令對象會自行處理所有細節工作。
下一步是讓所有命令實現相同的接口。 該接口通常只有一個沒有任何參數的執行方法, 讓你能在不和具體命令類耦合的情況下使用同一請求發送者執行不同命令。 此外還有額外的好處, 現在你能在運行時切換連接至發送者的命令對象, 以此改變發送者的行為。
你可能會注意到遺漏的一塊拼圖——請求的參數。 GUI 對象可以給業務層對象提供一些參數。 但執行命令方法沒有任何參數,所以我們如何將請求的詳情發送給接收者呢?答案是:使用數據對命令進行預先配置,或者讓其能夠自行獲取數據。
讓我們回到文本編輯器。 應用命令模式后, 我們不再需要任何按鈕子類來實現點擊行為。 我們只需在 按鈕 Button 基類中添加一個成員變量來存儲對于命令對象的引用, 并在點擊后執行該命令即可。
你需要為每個可能的操作實現一系列命令類, 并且根據按鈕所需行為將命令和按鈕連接起來。
其他菜單、 快捷方式或整個對話框等 GUI 元素都可以通過相同方式來實現。當用戶與 GUI 元素交互時,與其連接的命令將會被執行。 現在你很可能已經猜到了, 與相同操作相關的元素將會被連接到相同的命令,從而避免了重復代碼。
最后,命令成為了減少 GUI 和業務邏輯層之間耦合的中間層。而這僅僅是命令模式所提供的一小部分好處!
結構
代碼
#include <iostream>
#include <string>
#include <list>
#include <memory>
using namespace std;class Receiver{
public:void operation1(const string& params){cout<<"開始用"<<params<<"執行操作1"<<endl;}void operation2(const string& params){cout<<"開始用"<<params<<"執行操作2"<<endl;}
};class Command{
public:virtual void execute()=0;
};
class ConcreteCommand1:public Command{
public:ConcreteCommand1(shared_ptr<Receiver> receiver, const string& parameter):m_receiver(receiver),m_params(parameter){}void execute() override{m_receiver->operation1(m_params);}
private:shared_ptr<Receiver> m_receiver;string m_params;
};
class ConcreteCommand2:public Command{
public:ConcreteCommand2(shared_ptr<Receiver> receiver, const string& parameter):m_receiver(receiver),m_params(parameter){}void execute() override{m_receiver->operation2(m_params);}
private:shared_ptr<Receiver> m_receiver;string m_params;
};class Invoker{
public:void setCommand(shared_ptr<Command> command){m_commmandList.push_back(command);}void executeCommand(){for(auto& command:m_commmandList){command->execute();}}
private:list<shared_ptr<Command>> m_commmandList;
};int main(){auto receiver=make_shared<Receiver>();auto command1=make_shared<ConcreteCommand1>(receiver,"按鈕1");auto command2=make_shared<ConcreteCommand2>(receiver,"按鈕2");auto invoker=make_shared<Invoker>();invoker->setCommand(command1);invoker->executeCommand();cout<<"------------------"<<endl;invoker->setCommand(command2);invoker->executeCommand();cout<<"------------------"<<endl;return 0;
}