一、概念
- 命令模式:將“請求”封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式也支持可撤銷的操作。
- 角色:
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、需要支持撤消/重試操作。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用 undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供 redo()方法, 以供客戶端在需要時再重新實施命令效果。
4、使用命令模式作為 "回調(callBack)" 在面向對象系統中的替代。"callBack" 講的便是先將一個函數登記上,然后在以后調用此函數。
5、如果要將系統中所有的數據更新到日志里,以便在系統崩潰時,可以根據日志讀回所有的數據更新命令,重新調用 execute() 方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。