命令模式將請求傳送者與請求接收者解耦,在傳送者與接收者之間引入命令物件,將傳送者的請求封裝在命令物件中,請求傳送者通過命令物件來間接引用接收者,使得系統具有更好的靈活性,使用者可以根據需要為請求傳送者增加新的命令物件而無須修改原有系統
模式動機
舉個現實生活中的例子,開關是請求的傳送者,電燈是請求的接收者,它們之間不存在直接的耦合關係,而是通過電線連線到一起,開關不需要知道如何將開燈或關燈請求傳輸給電燈,而是通過電線來完成這項功能。
此時可以理解為電線充當封裝請求的命令物件,開關如果開則電線通電,並呼叫電燈的開燈方法,反之則關燈。不同電線可以連線不同的請求接收者,因此只需更換一根電線,相同的開關即可操作不同的電器裝置。
在軟體設計中,我們也像上述例子一樣,經常需要向某些物件傳送請求,但是不知道請求接收者是誰,也不知道被請求的操作是哪個,我們只需指定具體的請求接收者即可,此時,可以使用命令模式使請求傳送者與請求接收者消除彼此之間的耦合,讓物件之間的呼叫更加啊靈活。
模式定義
請一個請求封裝為一個物件,從而使我們可用不同請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作。命令模式是一種物件行為型模式,其別名為動作(Action)模式或事務(Transaction)模式
模式結構
-
Command(抽象命令類)
一般是介面,其中宣告瞭用於執行請求的 execute() 等方法,通過這些方法呼叫請求接收者的相關操作
-
ConcreteCommand(具體命令類)
是抽象命令類的子類,實現在抽象命令類中宣告的方法。它對應具體接收者物件,繫結接收者物件的動作。在實現 execute() 方法時將呼叫接收者物件的相關操作(Action)
-
Invoker(呼叫者)
即請求的傳送者,又稱請求者,通過命令物件來執行請求。一個呼叫者並不需要再設計時確定其接收者,因此它與抽象命令類之間只存在關聯關係。程式執行時呼叫具體命令物件的 execute() 方法,間接呼叫接收者的相關操作
-
Receiver(接收者)
執行者執行與請求相關的操作,它具體實現對請求的業務處理
-
Client(客戶類)
客戶類中需建立呼叫者物件和具體命令物件,再建立具體命令物件時指定其對應接收者,傳送者和接收者之間無直接關係,透過具體命令物件實現間接呼叫
模式分析
命令模式的本質其實就是將命令(Command)、發出命令的責任(Invoker)和執行命令的責任(Recevier)分隔開。請求的一方發出請求,要求執行一個操作,接收的一方收到請求,並執行操作,請求的一方不必知道接收請求一方的任何細節。
命令模式的關鍵在於引入抽象命令介面,呼叫者針(Invoker)對抽象命令介面程式設計,只有實現具體命令類才能與對應接收者相關聯。每個具體命令類把接收者(Recevier)作為一個例項變數儲存,從而指定接收者,並呼叫對應的請求處理方法
可以通過順序圖來進一步理解命令模式中物件之間的相互關係。
模式例項之電視機遙控器
電視機是請求接收者,遙控器是請求傳送者,遙控器上有一些不同按鈕,對應電視機的不同操作,分別是:開啟電視機、關閉電視機和切換頻道。
-
接收者類 Television(電視機類)
public class Televison { public void open() { System.out.println("開啟電視機"); } public void close() { System.out.println("關閉電視機"); } public void changeChannel() { System.out.println("切換電視訊道"); } }
-
抽象命令類 AbstractCommand(命令類)
public interface AbstractCommand { public void execute(); }
-
具體命令類 TVOpenCommand(電視機開啟命令類)
public class TVOpenCommand implements AbstractCommand { private Televison tv; public TVOpenCommand() { tv = new Televison(); } @Override public void execute() { tv.open(); } }
-
具體命令類 TVCloseCommand(電視機關閉命令類)
public class TVCloseCommand implements AbstractCommand { private Televison tv; public TVCloseCommand() { tv = new Televison(); } @Override public void execute() { tv.close(); } }
-
具體命令類 TVChangeCommand(電視機頻道切換命令類)
public class TVChangeCommand implements AbstractCommand { private Televison tv; public TVChangeCommand() { tv = new Televison(); } @Override public void execute() { tv.changeChannel(); } }
-
呼叫者類 Controller(遙控器類)
public class Controller { private AbstractCommand openCommand, closeCommand, changeCommand; public Controller(AbstractCommand openCommand, AbstractCommand closeCommand, AbstractCommand changeCommand) { this.openCommand = openCommand; this.closeCommand = closeCommand; this.changeCommand = changeCommand; } public void open() { openCommand.execute(); } public void change() { changeCommand.execute(); } public void close() { closeCommand.execute(); } }
-
客戶端測試類 Client
public class Client { public static void main(String[] args) { AbstractCommand openCommand, closeCommand, changeCommand; openCommand = new TVOpenCommand(); closeCommand = new TVCloseCommand(); changeCommand = new TVChangeCommand(); Controller controller = new Controller(openCommand, closeCommand, changeCommand); controller.open(); controller.change(); controller.close(); } }
-
執行結果
模式優缺點
命令模式優點:
- 降低系統耦合度
- 新的命令可以很容易地加入系統中
- 可以比較容易地設計一個設計一個命令佇列和巨集命令(組合命令)
- 可以方便實現對請求的 Undo 和 Redo
命令模式缺點:
- 使用命令模式可能導致某些系統有過多的具體命令類
撤銷操作的實現
我們可以通過對命令類進行修改使得系統支援撤銷操作和恢復操作,,抽象命令類(AbstractCommand)宣告一個 undo() 方法
public interface AbstractCommand {
public void undo();
public void execute();
}
具體命令類(ConcreteCommand)實現在抽象命令類(AbstractCommand)中宣告的 execute() 和 undo() 方法
public class ConcreteCommand implements AbstractCommand {
private Receiver receiver;
public ConcreteCommand() {
receiver = new Receiver();
}
@Override
public void execute() {
receiver.method;
}
@Override
public void undo() {
// 撤銷 execute() 操作
}
}
呼叫者(Invoker)照常引用一個抽象命令 AbstractCommand 型別的物件 command,通過該 command 物件間接呼叫接收者 Receiver 類的業務方法
public class Invoker {
private AbstractCommand command;
public Invoker(AbstractCommand command) {
this.openCommand = openCommand;
}
public void method() {
command.execute();
}
public void undo() {
// 撤銷操作
}
}
上述例項只能實現一步撤銷操作,因為沒有儲存命令物件的歷史狀態,可以通過引入一個命令集合或其他方式來儲存中間狀態,從而實現多次撤銷操作
巨集命令
巨集命令又稱組合命令,它是命令模式和組合模式聯用的產物。巨集命令也是一個具體命令,不過它包含了對其他命令物件的引用,在呼叫巨集命令的 execute() 方法,將遞迴呼叫它所包含的每個成員命令的 execute() 方法。一個巨集命令的成員物件可以是簡單命令,也可以繼續是巨集命令。