定義
命令模式(Command Pattern):將一個請求封裝爲一個對象,從而可用不同的請求對客戶端進行參數化,對請求排隊或者記錄請求日誌,以及支持可撤銷的操作。
結構
- Command(抽象命令類):抽象命令類一般是一個抽象類或接口,在其中聲明瞭用於執行請求的execute()等方法,通過這些方法可以調用請求接受者的相關操作。
- ConcreteCommand(具體命令類):具體命令類是抽象命令類的子類,實現了在抽象命令類中聲明的方法,它對應具體的接收者對象,將接受者對象的動作綁定其中。具體命令類在實現execute()方法時將調用接收者對象的相關操作(Action)。
- Invoker(調用者):調用者即請求發送者,它通過命令對象來執行請求。一個調用者並不需要在設計時確定其接收者,因此它只與抽象命令類之間存在關聯關係。在程序運行時可以將一個具體命令對象注入其中,再調用具體命令對象的execute()方法,從而實現間接調用請求接收者的相關操作。
- Receiver(接收者):接收者執行與請求相關的操作,具體實現對請求的業務處理。
代碼
Command
public abstract class Command {
public abstract void execute();
}
Receiver
public class Receiver {
public void action() {
// 具體操作
System.out.println("action");
}
}
ConcreteCommand
public class ConcreteCommand extends Command {
private Receiver receiver;
public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
receiver.action();
}
}
Invoker
public class Invoker {
private Command command;
public Invoker(Command command) {
this.command = command;
}
// 業務方法,用於調用命令類的execute()
public void call() {
command.execute();
}
public void setCommand(Command command) {
this.command = command;
}
}
Test
public class Test {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(command);
invoker.call();
}
}
時間命令隊列
有時候,當一個請求發送者發送一個請求時有不止一個請求接收者產生響應,這些請求接收者將逐個執行業務方法,完成對請求的處理,此時可以通過命令隊列來實現。
CommandQueue:命令隊列
import java.util.ArrayList;
import java.util.List;
public class CommandQueue {
/** 定義一個List來存儲命令隊列 */
private List<Command> commandList = new ArrayList<>();
public void addCommand(Command command) {
commandList.add(command);
}
public void removeCommand(Command command) {
commandList.remove(command);
}
public void execue() {
// 循環調用
commandList.forEach(Command::execute);
}
}
Invoker:請求發送者
public class Invoker1 {
// 維持一個命令隊列的引用
private CommandQueue commandQueue;
// 構造注入
public Invoker1(CommandQueue commandQueue) {
this.commandQueue = commandQueue;
}
// 調用
public void call() {
commandQueue.execue();
}
}
實現撤銷操作
在命令模式中可以通過對命令類進行修改使得系統支持(Undo)操作和恢復(Redo)操作,下面通過一個簡單實例來學習如何使用命令模式中實現撤銷操作。
設計一個簡易計算器,該計算器可以實現簡單的數學操作,還可以對運算結果實施撤銷操作。
Adder充當請求接收者
public class Adder {
private int num;
// 實現加法操作
public int add(int value) {
num += value;
return num;
}
}
AbstractCommand充當抽象命令,聲明瞭execute()和撤銷undo()
public abstract class AbstractorCommand {
// 執行方法
public abstract int execute(int value);
// 撤銷
public abstract int undo();
}
AddCommand充當具體抽象類
public class AddCommand extends AbstractorCommand {
private Adder adder = new Adder();
private int value;
// 調用加法類的加法操作
@Override
public int execute(int value) {
this.value = value;
return adder.add(value);
}
// 加一個相反數實現加法的逆向操作
@Override
public int undo() {
return adder.add(-value);
}
}
CalculatorForm充當請求發送者
public class CalculatorForm {
private AbstractorCommand command;
public CalculatorForm(AbstractorCommand command) {
this.command = command;
}
// 調用命令對象的execute()方法執行運算
public void compute(int value) {
int i = command.execute(value);
System.out.println("運算結果:" + i);
}
public void undo() {
int i = command.undo();
System.out.println("撤銷操作,結果:" + i);
}
}
客戶端調用
public class Client {
public static void main(String[] args) {
AbstractorCommand command = new AddCommand();
CalculatorForm form = new CalculatorForm(command);
form.compute(10);
form.compute(5);
form.compute(10);
form.undo();
}
}
運行結果
運算結果:10
運算結果:15
運算結果:25
撤銷操作,結果:15
優/缺點與適用環境
- 優點
- 降低系統的耦合度。由於請求者與接收者之間不存在直接引用,因此請求者與接收者之間實現完全解耦,相同的請求者可以對應不同的接收者,同樣相同的接收者也可以供不同的請求者使用,兩者之間具有良好的獨立性。
- 新的命令可以很容易地加入到系統中。由於增加新的具體命令類不會影響到其他類,因此增加新的具體命令類很容易,無須修改原有系統源代碼,滿足開閉原則的要求。
- 可以較容易地設計一個命令隊列或宏命令(組合命令)。
- 爲請求的撤銷(Undo)和恢復(Redo)操作提供了一種設計和實現方案。
- 缺點
- 使用命令模式可能會導致系統有過多的具體命令類。因此針對每一請求接收者的調用操作都需要設計一個具體命令類,所以在某些系統中可能需要提供大量的具體命令類,這會影響命令模式的使用。
- 適用環境
- 系統需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互。請求調用者無需知道接收者的存在,與無序知道接收者是誰,接收者也無須關心何時被調用。
- 系統需要在不同的時間指定請求、將請求排隊和執行請求。一個命令對象的請求的初始調用者可以有不同的生命週期,換而言之,最初的請求發送者可能已經不在了,而命令對象本身仍然是活動的,可以通過該命令對象去調用請求接收者,而且無需關心請求調用者的存在性,可以通過請求日誌文件等機制來實現具體操作。
- 系統需要支持命令的撤銷(Undo)操作和恢復(Redo)操作
- 系統需要將一組操作組合在一起形成宏命令。