命令模式.
一、概念
- 命令模式:將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。
- 角色:
1、命令(Command):為所有命令聲明瞭一個介面。呼叫命令物件的 execute()方法,就可以讓接收者進行相關的操作。這個介面也具備一個 undo() 方法。
2、具體命令(ConcreteCommand):實現命令介面,定義了動作和接收者之間的繫結關係。呼叫者只要呼叫 execute() 就可以發出請求,然後由 ConcreteCommand 呼叫接收者的一個或多個動作。
3、請求者(Invoker):持有一個命令物件,有一個行動方法,在某個時間點呼叫命令物件的 execute() 方法,將請求付諸實行。
4、接收者(Receiver):接收者知道如何進行必要的動作,實現這個請求。任何類都可以當接收者。
5、客戶端(Client):建立一個具體命令(ConcreteCommand)物件並確定其接收者,包括把其他角色串連在一起。
二、Demo 實現
Topic:我們要製作一個簡易的遙控器,有兩個控制燈開關的按鈕,並有一個操作回退按鈕。
1、接收者
首先,我們先來定義一個接收者的角色,也就是最後執行動作的那個物件 —— Light.java,控制著燈的開啟和關閉。
public class Light { public void on() { System.out.println("燈亮了..."); } public void off() { System.out.println("燈暗了..."); } }
2、命令
現在,我們要定義一個命令角色。一般是一個介面,為所有的命令物件宣告一個介面,規範將要進行的命令操作。
public interface Command { /** * 執行命令 */ void execute(); /** * 撤銷命令 */ void undo(); }
3、具體命令
有了命名角色後,我們要構建具體命令角色。具體命令實現了命令介面,定義了動作和接收者之間的繫結關係。這裡,我們有兩個具體命令物件—— LightOnCommand.java(開燈命令)、LightOffCommand.java(關燈命令)
public class LightOnCommand implements Command { private Light light; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { light.on(); } @Override public void undo() { light.off(); } }
public class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { light.off(); } @Override public void undo() { light.on(); } }
4、請求者
前面,我們定義了動作的接收方和聯絡中介 —— 命名物件。現在,我們要著手構建請求者角色了。請求者持有一個命令物件,有一個行動方法。它會在某個時間點執行行動方法,但不關心是誰具體執行了這個動作。
public class RemoteInvoker { /** * 開關命令陣列,模擬有很多對開關陣列 */ private Command[] onCommands; private Command[] offCommands; /** * 撤銷(回退)命令 */ private Command undoCommand; public RemoteInvoker(int length) { // 有幾組開關,就設定多少陣列 onCommands = new Command[length]; offCommands = new Command[length]; // 把每個命令初始化成空命令,避免空指標異常 Command noCommand = new NoCommand(); undoCommand = noCommand; for (int i = 0; i < length; i++) { onCommands[i] = noCommand; offCommands[i] = noCommand; } } /** * @Description 設定命令物件 * @date 2018/11/29 09:15 * @param slot 遙控器的位置 * @param onCommand 開的命令 * @param offCommand 關的命令 * @return void */ public void setCommond(int slot, Command onCommand, Command offCommand) { onCommands[slot] = onCommand; offCommands[slot] = offCommand; } public void onButton(int slot) { onCommands[slot].execute(); //為撤銷(回退)按鈕記錄動作 undoCommand = onCommands[slot]; } public void offButton(int slot) { offCommands[slot].execute(); //為撤銷(回退)按鈕記錄動作 undoCommand = offCommands[slot]; } public void undoButton() { undoCommand.undo(); } }
5、客戶端
前面,我們定義好了請求者、接收者已經兩者之間的聯絡中介 —— 命令物件。但是這幾個角色物件之間都是鬆耦合的,還沒有一個具體動作的流程,現在我們利用客戶端角色把整個動作流程串聯在一起。
public class RemoteClient { public static void main(String[] args) { // 1、建立接收者 Light light = new Light(); // 2、建立命令物件 LightOnCommand lightOnCommand = new LightOnCommand(light); LightOffCommand lightOffCommand = new LightOffCommand(light); // 3、建立一組開關並用命令物件裝載它 RemoteInvoker invoker = new RemoteInvoker(1); invoker.setCommond(0, lightOnCommand, lightOffCommand); // 4、測試 invoker.onButton(0); invoker.offButton(0); invoker.undoButton(); } }
三、總結
- 命令模式將發出請求的物件和執行請求的物件解耦,在被解耦的兩者之間是通過命令物件進行溝通的。
- 一個命令物件通過在特定接收者上繫結一組動作來封裝一個請求。要達到這一點,命令物件將接收者和動作封裝進物件中,這個物件只暴露出一個 execute() 方法,當此方法被呼叫時,接收者就會進行這些動作。從外面來看,其他物件不知道究竟哪個接收者進行了哪些操作,只知道如果呼叫 execute() 方法,請求的目的就可以達到。
- 當你不想返回一個有意義的物件時,空物件就很有用。這樣,我們就可以把處理 null 的責任轉移給空物件,甚至有些時候,空物件本身也被視為一種設計模式。
- 我們還可以把一堆命令組裝起來拼成一個命令,稱為巨集命令。巨集命令是命令的一種延伸,允許呼叫一系列的命令。包括一系列的執行和撤銷動作。
- 適用場景:
1、命令的傳送者和命令執行者有不同的生命週期。命令傳送了並不是立即執行。
2、命令需要進行各種管理邏輯,比如:對多個命令的統一控制。
3、需要支援撤消/重做操作。