設計模式——命令模式
命令模式定義
將“請求”封裝成對象,以便使用不同的請求、隊列或者日志來參數化其他對象。命令模式也支持可撤銷的操作。
命令模式可將“動作的請求者”從“動作的執行者”對象中解耦。
這個模型允許將“發出請求的對象”和“接受與執行這些請求的對象”分隔開來。
命令模式的類圖:
類圖角色:
Client:負責創建一個具體的命令對象ConcreteCommand,并設置這個命令對象的接收者。創建調用者對象,并在調用者對象上調用setCommand()方法,傳入命令對象,把命令對象存儲在其中,以后需要用到。
**Receiver:**接收者,任何類都可以充當。接收者知道如何進行必要的工作。
ConcreteCommand:這個命令對象定義了動作和接收者之間的綁定關系。調用者調用命令對象的execute()方法發出請求。然后由命令對象調用接收者的一個或多個動作響應請求。
Command:為所有命令對象定義了一個接口。所有命令對象都必須實現這個接口。調用命令對象的execute方法就可以讓接收者進行相關的動作。調用undo方法就可以撤銷該動作。
Invoker:調用者持有命令對象,并在某個時間點調用命令對象的execute方法,將請求負諸實行。
命令模式的優勢就是允許將“發出請求的對象“與”接受與執行這些請求的對象“分隔開來。比如說對于遙控器API,我們需要分隔開”發出請求的按鈕代碼“和“執行請求的特定對象”。遙控器的每個插槽都對應一個命令,那么當按下一個按鈕時,相應的命令對象的execute方法就會被調用,其結果就是接收者動作被調用。遙控器不需要知道事情是怎么發生的,也不需要知道涉及哪些對象。
當接受與執行這些請求的對象(即接收者)越復雜,涉及到的其他對象越多,命令模式的優勢越是明顯。
下面我們就用遙控器控制電燈的開與關來舉例說明命令模式:
1.定義一個接收者,此處就是電燈,:
package target; public class Light { public void on(){ System.out.println("light is on"); } public void off(){ System.out.println("light is off"); } }
1
2
3
4
5
6
7
8
9
10
11
2.定義一個命令接口,所有的命令對象都必須實現這個接口:
package inter; public interface Command { void execute();//執行動作 void undo();//撤銷該動作 }
1
2
3
4
5
6
3.定義兩個具體的命令對象:開命令對象、關命令對象:
開命令對象:對開命令對象而言,execute執行的是開動作,那么undo方法就是撤銷該動作,即恢復關的狀態。
package impl; import inter.Command; import receiver.Light; 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(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
關命令對象:對關命令對象而言,execute執行的是關動作,那么undo方法就是撤銷該動作,即恢復開的狀態。
package impl; import inter.Command; import receiver.Light; 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(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
我們最好再定義一個默認的命令對象,這個對象什么都不做:
package impl; import inter.Command; public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
4.調用者對象
package invoker; import impl.NoCommand; import inter.Command; public class RemoteController { private Command[] onCommands; //按鈕很多,因此設置成數組,開命令對象數組 private Command[] offCommands;//關命令對象數組 private Command undoCommand;//前一個命令將被記錄在此 ,用于做撤銷用的 public RemoteController(){ onCommands = new Command[3]; offCommands = new Command[3]; for(int i = 0 ;i < 3; i++){ onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } undoCommand = new NoCommand(); } //傳入命令對象,把命令對象存儲在其中,以后需要用到 public void setCommand(int slot,Command onCommand,Command offCommand){ if(slot < 0 || slot >= 3) return; 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 = offCommands[slot]; } //撤銷操作 public void undoButtonWasPushed(){ undoCommand.undo(); } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
5.測試
import impl.LightOffCommand; import impl.LightOnCommand; import inter.Command; import invoker.RemoteController; import receiver.Light; public class Main { public static void main(String[] args) { Light light = new Light(); Command onCommand = new LightOnCommand(light); Command offCommand = new LightOffCommand(light); RemoteController remoteController = new RemoteController(); remoteController.setCommand(0,onCommand,offCommand); remoteController.onButtonWasPushed(0); //開燈 remoteController.undoButtonWasPushed();//撤銷 remoteController.offButtonWasPushed(0); //關燈 remoteController.undoButtonWasPushed();//撤銷 } }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
6.測試結果
light is on light is off light is off light is on
1
2
3
4
demo代碼
命令模式的更多用途
1.隊列請求
如有這樣一個隊列,我們可以在某一端添加命令,然后另一端是線程。線程進行下面的動作:從隊列中取出一個命令,調用它的execute方法,等待這個調用完成,然后將此命令對象丟棄,再取出下一個命令,直到隊列為空。這個過程中,工作隊列類和進行計算的對象之間完全是解耦的,此刻線程可能在做著復雜的計算,或者可能正在讀取網絡數據等。
工作隊列對象不在乎到底做些什么,它們只知道取出命令對象,然后調用其execute方法。類似地,它們只要是實現命令模式的對象,就可以放入隊列里,當線程可用時,就調用此對象的execute方法。
2.日志請求
某些應用需要我們把所有的動作都記錄在日志里,并能在系統死機后,重新調用這些動作恢復到之前的狀態。通過新增兩個方法(store()、load()),命令模式就能夠支持這一點。在Java中,我們可以利用對象的序列化(Serialization)實現這些方法,但是一般認為序列化最好還是用在對象的持久化上。
當我們執行命令時,交歷史記錄儲存在磁盤中。一旦死機,我們就可以將命令對象重新加載,并成批地依次地調用這些對象的execute方法。對許多調用大型數據結構的動作的應用無法在每次改變發生時被快速地存儲。通過使用記錄日志,我們可以將上次檢查點之后的所有操作記錄下來,如果系統出狀況,從檢查點開始應用這些操作。比方說,對于電子表格應用,我們可能想要實現的錯誤恢復方式是將電子表格的操作記錄在日志中,而不是每次電子表格一有變化就記錄整個電子表格。對更高級的應用而言,這些技巧可以被擴展應用到事務處理中,也就是說,一整群操作必須全部進行完成,或者沒有進行任何的操作。
小結
命令模式將發出請求的對象和執行請求的對象解耦。
在被解耦的兩者之間是通過命令對象進行溝通的。命令對象封裝了接收者和一個或一組動作。
調用者通過調用命令對象的execute()方法發出請求,這會使得接收者的動作被調用。
調用者可以接受命令當做參數,甚至在運行時動態地進行。
命令可以支持撤銷,做法是實現一個undo()方法來回到execute()被執行前的狀態。
宏命令是命令的一種簡單的延伸,允許調用多個命令。宏方法也可以支持撤銷。
命令也可以用來實現日志和事務系統。
謝謝閱讀。
數據結構
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。