命令模式(Command):將一個請求封裝成一個對象,使得你用不同的請求把客戶端參數化,對請求排隊或者記錄請求日誌,可以提供命令的撤銷和恢復功能。
命令模式,顧名思義來理解即可,就是客戶端發佈一個命令(也就是“請求”),而這個命令是已經被封裝成一個對象的。即這個命令對象的內部可能已經指定了該命令具體被誰負責執行。就像開發經理從客戶那邊獲取對方的需求(命令),客戶在描述具體的需求可以決定是否明確指出該需求的執行方。
命令模式的通用類圖如下:
上圖中,Invoker 類就相當於開發經理,ConcreteCommand 類是具體的命令,它繼承自抽象命令類 Command 類,該抽象類中定義了每個命令被執行的方法 execute() 。Receiver 抽象類定義了對每一個具體的命令的執行方法 action() ,一旦接收到命令則立即行動。這裏應該注意的是,每個具體的命令類都必須指定該命令的接收者,否則這命令發佈了也沒相應的人來完成,那就沒戲了。
具體代碼實現如下:
命令接收者相關類:
- //抽象接收者,定義了每個接收者應該完成的業務邏輯
- abstract class AbstractReceiver {
- public abstract void doJob();
- }
- // 具體接收者01,實現自己真正的業務邏輯
- class Receiver01 extends AbstractReceiver {
- public void doJob() {
- System.out.println("接收者01 完成工作 ...\n");
- }
- }
- // 具體接收者02,實現自己真正的業務邏輯
- class Receiver02 extends AbstractReceiver {
- public void doJob() {
- System.out.println("接收者02 完成工作 ...\n");
- }
- }
命令類:
- // 抽象命令類,定義了每個具體命令被執行的入口方法execute()
- abstract class AbstractCommand {
- public abstract void execute();
- }
- // 具體命令類01,通過構造函數的參數決定了該命令由哪個接收者執行
- class Command01 extends AbstsractCommand {
- private AbstractReceiver receiver = null;
- public Command01(AbstractReceiver receiver) {
- this.receiver = receiver;
- }
- public void execute() {
- System.out.println("命令01 被髮布 ...");
- this.receiver.doJob();
- }
- }
- // 具體命令類02,通過構造函數的參數決定了該命令由哪個接收者執行
- class Command02 extends AbstractCommand {
- private AbstractReceiver receiver = null;
- public Command02(AbstractReceiver receiver) {
- this.receiver = receiver;
- }
- public void execute() {
- System.out.println("命令02 被髮布 ...");
- this.receiver.doJob();
- }
- }
調用者類:
- // 調用者,負責將具體的命令傳送給具體的接收者
- class Invoker {
- private AbstractCommand command = null;
- public void setCommand(AbstractCommand command) {
- this.command = command;
- }
- public void action() {
- this.command.execute();
- }
- }
測試類:
- //測試類
- public class Client {
- public static void main(String[] args) {
- // 創建調用者
- Invoker invoker = new Invoker();
- // 創建一個具體命令,並指定該命令被執行的具體接收者
- AbstractCommand command01 = new Command01(new Receiver01());
- // 給調用者發佈一個具體命令
- invoker.setCommand(command01);
- // 調用者執行命令,其實是將其傳送給具體的接收者並讓其真正執行
- invoker.action();
- AbstractCommand command02 = new Command01(new Receiver02());
- invoker.setCommand(command02);
- invoker.action();
- }
- }
測試結果:
命令01 被髮布 ...
接收者01 完成工作 ...
命令02 被髮布 ...
接收者02 完成工作 ...
|
如上面測試中輸出的結果,我們知道在客戶端中每次聲明並創建一個具體的命令對象時總要顯式地將其指定給某一具體的接收者(也就是命令的最終執行者),這似乎不太靈活,在現實中有些命令的發佈也確實不是預先就指定了該命令的接收者的。
我們可以修改一下類圖,使得客戶端在有必要的時候才顯式地指明命令的接收者,如下:
較之第一個通用類圖,這裏的客戶 Client 類不直接與接收者 Receiver 類相關,而僅僅與調用者 Invoker 類有聯繫,客戶發佈下來的一個命令或者請求,只需要到了調用者Invoker 這裏就停止了,具體怎麼實現,不必對客戶公開,由調用者分配即可。這裏的 Command 抽象類將子類中指定具體接收者的構造函數的邏輯提取出來,由具體子類提供通過調用父類構造函數的無參、有參構造函數來實現。主要修改的是命令相關的類。
具體邏輯請看下面的代碼實現:
命令類:
- /*
- * 抽象命令類,使用構造函數的傳入參數預先內定具體接收者, 若想使用其他接收者,可在子類的構造函數中傳入
- */
- abstract class AbstractCommand {
- protected AbstractReceiver receiver = null;
- public AbstractCommand(AbstractReceiver receiver) {
- this.receiver = receiver;
- }
- public abstract void execute();
- }
- // 具體命令類01,提供無參、有參兩種構造函數
- class Command01 extends AbstractCommand {
- // 使用無參構造函數來默認使用的具體接收者
- public Command01() {
- super(new Receiver01());
- }
- // 使用有參構造函數來指定具體的接收者
- public Command01(AbstractReceiver receiver) {
- super(receiver);
- }
- public void execute() {
- System.out.println("命令01 被髮布 ...")
- this.receiver.doJob();
- }
- }
- // 具體命令類02,提供無參、有參兩種構造函數
- class Command02 extends AbstractCommand {
- // 使用無參構造函數來默認使用的具體接收者
- public Command02() {
- super(new Receiver02());
- }
- // 使用有參構造函數來指定具體的接收者
- public Command02(AbstractReceiver receiver) {
- super(receiver);
- }
- public void execute() {
- System.out.println("命令02 被髮布 ...")
- this.receiver.doJob();
- }
- }
修改後的測試類:
- // 測試類
- public class Client {
- public static void main(String[] args) {
- // 創建調用者
- Invoker invoker = new Invoker();
- // 創建一個具體命令,並指定該命令被執行的具體接收者
- // AbstractCommand command01 = new Command01(new Receiver01());
- AbstractCommand command01 = new Command01();
- // 給調用者發佈一個具體命令
- invoker.setCommand(command01);
- // 調用者執行命令,其實是將其傳送給具體的接收者並讓其真正執行
- invoker.action();
- // AbstractCommand command02 = new Command01(receiver02);
- AbstractCommand command02 = new Command02();
- invoker.setCommand(command02);
- invoker.action();
- System.out.println("\n設置命令01由接收者02執行...");
- command01 = new Command01(new Receiver02());
- invoker.setCommand(command01);
- invoker.action();
- }
- }
測試結果:
命令01 被髮布 ...
接收者01 完成工作 ...
命令02 被髮布 ...
接收者02 完成工作 ...
設置命令01由接收者02執行...
命令01 被髮布 ...
接收者02 完成工作 ...
|
此時在客戶端中,我們不指明一個命令的具體接收者(執行者)也同樣可以達到第一種實現方法中的效果。此外,客戶也可以顯式指出具體接收者,就像上面那樣。
命令模式的優點:
1、 調用者與接收者沒有任何的依賴關係,它們時通過具體的命令的存在而存在的;
2、 若有多個具體命令,只要擴展 Command 的子類即可,同樣地具體接收者也可以相對應地進行擴展;
命令模式的缺點:其實上面優點中第2點在一定場景中也會變成缺點。如果具體的命令有很多個,那麼子類就必然會暴增、膨脹。
但是,上面的具體代碼實現中這種設計似乎也不太樂觀,原因是每一個具體的命令都是由一個具體的接收者來執行的,在多交互的場景中這顯然是太理想化的。於是,我想到了中介者模式(Mediator)中最主要的 Mediator 類中預先地註冊了業務中需要交互的同事類的對象,接下來每一次交互邏輯都交給 Mediator 來“暗箱”操作。
根據上一段的想法,我們可以在抽象命令 Command 類中預先註冊一定數量的具體接收者,那麼具體命令中就可以決定是否要在多個接收者中進行協作完成了,這種協作的代碼邏輯則應該寫在覆蓋父類的execute() 方法中,而在 execute() 方法中又可以運用模板方法模式(Template Method)進行設計。