前言:
來源於《head first 設計模式》。當作讀書筆記了,這次看的是第六章裝命令模式。
命令模式的概念
將請求封裝成對象,這可以讓你使用不同的請求、隊列、或者日誌請求來參數化其他對象,命令模式也支持撤銷操作,當需要將發出請求和執行請求的對象解耦的時候,使用命令模式即可。
命令模式的uml圖
簡單介紹一下:
- 命令(Command):爲所有命令聲明瞭一個接口。調用命令對象的 execute()方法,就可以讓接收者進行相關的操作。
- 具體命令(ConcreteCommand):實現命令接口,定義了動作和接收者之間的綁定關係。調用者只要調用 execute() 就可以發出請求,然後由 ConcreteCommand 調用接收者的一個或多個動作。
- 請求者(Invoker):持有一個命令對象或者多個命令對象,有一個行動方法,在某個時間點調用命令對象的 execute() 方法,將請求付諸實行。
- 接收者(Receiver):接收者知道如何進行必要的動作,實現這個請求。任何類都可以當接收者。這個可以看作是最終的執行者
- 客戶端(Client):創建一個具體命令(ConcreteCommand)對象並確定其接收者(Receiver),包括把其他角色串連在一起。
看個例子把。
模擬一個遙控器,控制燈的開關.
定義接受者
這是最後執行動作的角色。
public class Light {
String location = "";
public Light(String location) {
this.location = location;
}
public void on() {
System.out.println(location + " light is on");
}
public void off() {
System.out.println(location + " light is off");
}
}
command接口
public interface Command {
/**
* 執行命令
*/
void execute();
/**
* 撤銷命令
*/
void undo();
}
ConcreteCommand具體命令類
具體的兩個命令,分別定義其撤銷操作
關燈命令
public class LightOffCommand implements Command {
Light light;
public LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
@Override
public void undo() {
light.on();
}
}
開燈命令
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();
}
}
請求者(Invoker)
onCommands offCommands用於存儲命令對象,通過onButtonWasPushed來調用命令的execute 命令中再把處理交給接受者執行後面的操作
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
/**
* 撤銷(回退)命令
*/
private Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
public void onButtonWasPushed(int slot) {
onCommands[slot].execute();
undoCommand=onCommands[slot];
}
public void offButtonWasPushed(int slot) {
offCommands[slot].execute();
undoCommand=onCommands[slot];
}
public void undoButton() {
undoCommand.undo();
}
public String toString() {
StringBuffer stringBuff = new StringBuffer();
stringBuff.append("\n------ Remote Control -------\n");
for (int i = 0; i < onCommands.length; i++) {
stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
+ " " + offCommands[i].getClass().getName() + "\n");
}
return stringBuff.toString();
}
}
客戶端(遙控器)
前面,我們定義好了請求者、接收者已經兩者之間的聯繫中介 —— 命令對象。但是這幾個角色對象之間都是鬆耦合的,還沒有一個具體動作的流程,現在我們利用客戶端角色把整個動作流程串聯在一起。
public class RemoteLoader {
public static void main(String[] args) {
RemoteControl remoteControl = new RemoteControl();
Light livingRoomLight = new Light("Living Room");
LightOnCommand livingRoomLightOn =
new LightOnCommand(livingRoomLight);
LightOffCommand livingRoomLightOff =
new LightOffCommand(livingRoomLight);
remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
System.out.println(remoteControl);
remoteControl.onButtonWasPushed(0);
remoteControl.offButtonWasPushed(0);
remoteControl.undoButton();
}
}
總結
命令模式將發出請求的對象和執行請求的對象解耦,在被解耦的兩者之間是通過命令對象進行溝通的。
- 一個命令對象通過在特定接收者上綁定一組動作來封裝一個請求。要達到這一點,命令對象將接收者和動作封裝進對象中,這個對象只暴露出一個 execute() 方法,當此方法被調用時,接收者就會進行這些動作。從外面來看,其他對象不知道究竟哪個接收者進行了哪些操作,只知道如果調用 execute() 方法,請求的目的就可以達到。
- 當你不想返回一個有意義的對象時,空對象就很有用。這樣,我們就可以把處理 null 的責任轉移給空對象,甚至有些時候,空對象本身也被視爲一種設計模式。例如本例子中的noCommand
適用場景:
1、命令的發送者和命令執行者有不同的生命週期,命令發送了並不是立即執行。換言之,原先的請求發出者可能已經不在了,而命令對象本身仍然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另外一個地址。命令對象可以在序列化之後傳送到另外一臺機器上去。
2、命令需要進行各種管理邏輯,比如:對多個命令的統一控制。
3、需要支持撤消/重試操作。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用 undo()方法,把命令所產生的效果撤銷掉。命令對象還可以提供 redo()方法, 以供客戶端在需要時再重新實施命令效果。
命令模式的更多用途
命令模式的關鍵之處就是把請求封裝成爲對象,也就是命令對象(一個接收者和一組動作),然後將它傳來傳去,就像是一般的對象一樣。現在,即使在命令對象被創建許久之後,運算依然可以被調用。事實上,它甚至可以在不同的線程中被調用。我們可以利用這樣的特性衍生一些應用,例如:線程池、工作隊列、日誌請求等。
隊列請求
想象有一個工作隊列:你在某一端添加命令,然後另一端則是線程。線程進行下面的動作:從隊列中取出一個命令,調用它的execute()方法,等待這個調用完成,然後將此命令對象丟棄,再取出下一個命令…
請注意,工作隊列和命令對象之間是完全解耦的。此刻線程可能在進行財務運算,下一刻卻在讀取網絡數據。工作隊列對象不在乎到底做些什麼,它們只知道取出命令對象,然後調用其execute()方法。類似地,它們只要實現命令模式的對象,就可以放入隊列裏,當線程可用時,就調用此對象的execute()方法。
日誌請求
某些應用需要我們將所有的動作都記錄在日誌中,並能在系統死機之後,重新調用這些動作恢復到之前的狀態。