JAVA設計模式--命令模式
阿新 • • 發佈:2019-01-22
一、什麼是命令式
命令(Command)模式又叫作動作(Action)模式或事務(Transaction)模式,是一種物件的行為模式。將一個請求封裝為一個物件,從而使你可用不同的請求對客戶進行引數化;對請求排隊或記錄請求日誌,以及支援可撤消的操作。
命令模式的本質:封裝請求
設計意圖:
命令模式通過將請求封裝到一個命令(Command)物件中,實現了請求呼叫者和具體實現者之間的解耦。
二、命令模式的適用性
在以下條件下可以考慮使用命令模式:
• 如果需要抽象出需要執行的動作,並引數化這些物件,可以選用命令模式。將這些需要執行的動作抽象成為命令,然後實現命令的引數化配置。
• 如果需要在不同的時刻指定、排列和執行請求,可以選用命令模式。將這些請求封裝成為命令物件,然後實現將請求佇列化。• 如果需要支援取消操作,可以選用命令模式,通過管理命令物件,能很容易地實現命令的恢復和重做功能。
• 如果需要支援當系統崩潰時,能將系統的操作功能重新執行一遍,可以選用命令模式。將這些操作功能的請求封裝成命令物件,然後實現日誌命令,就可以在系統恢復以後,通過日誌獲取命令列表,從而重新執行一遍功能。
• 在需要事務的系統中,可以選用命令模式。命令模式提供了對事務進行建模的方法。命令模式有一個別名就是Transaction。
三、命令模式的結構
命令模式涉及的角色及其職責如下:
抽象命令(Command)角色:一般定義為介面,用來定義執行命令的介面。
具體命令(ConcreteCommand)角色:通常會持有接收者物件,並呼叫接收者物件的相應功能來完成命令要執行的操作。接收者(Receiver)角色:真正執行命令的物件。任何類都可能成為接收者,只要它能夠實現命令要求實現的相應功能。
呼叫者(Invoker)角色:要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件。這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令物件的入口。
客戶端(Client)角色:建立具體的命令物件,並且設定命令物件的接收者。
命令模式結構示意原始碼如下:
先來看看抽象命令介面的定義。示例程式碼如下:
/** * 命令介面 */ public interface Command { /** * 執行命令 */ public void execute(); }
接下來看看具體命令是如何實現的。示例程式碼如下:
/** * 具體的命令實現 */ public class ConcreteCommand implements Command { /** * 持有相應的接收者物件 */ private Receiver receiver = null; /** * 構造方法,傳入相應的接收者物件 * * @param receiver 相應的接收者物件 */ public ConcreteCommand(Receiver receiver) { this.receiver = receiver; } /** * 執行命令 */ @Override public void execute() { // 通常會轉調接收者物件的相應方法,讓接收者來真正執行功能 receiver.action(); } }
再來看看接收者的定義。示例程式碼如下:
/** * 命令的接收者 */ public class Receiver { /** * 示意方法,真正執行命令相應的操作 */ public void action() { System.out.println("接收者開始行動。。。"); } }
下面該來看看呼叫者如何實現的了。示例程式碼如下:
/** * 命令的呼叫者 */ public class Invoker { /** * 持有命令物件 */ private Command command = null; /** * 設定呼叫者持有的命令物件 * * @param command 命令物件 */ public void setCommand(Command command) { this.command = command; } /** * 示意方法,呼叫命令執行請求 */ public void invoke() { command.execute(); } }
再來看看客戶端的實現。
public class Client { public static void main(String[] args) { // 建立接收者 Receiver receiver = new Receiver(); // 建立命令物件,設定它的接收者 Command command = new ConcreteCommand(receiver); // 建立呼叫者,把命令物件設定進去 Invoker invoker = new Invoker(); invoker.setCommand(command); // 呼叫者呼叫命令 invoker.invoke(); } }
四、命令模式的優點
更鬆散的耦合
命令模式使得發起命令的物件——客戶端,和具體實現命令的物件——接收者物件完全解耦,也就是說發起命令的物件完全不知道具體實現物件是誰,也不知道如何實現。
更動態的控制
命令模式把請求封裝起來,可以動態地對它進行引數化、佇列化和日誌化等操作,從而使得系統更靈活。
很自然的複合命令
命令模式中的命令物件能夠很容易地組合成複合命令,如巨集命令,從而使系統操作更簡單,功能更強大。
更好的擴充套件性
由於發起命令的物件和具體的實現完全解耦,因此擴充套件新的命令就很容易,只需要實現新的命令物件,然後在裝配的時候,把具體的實現物件設定到命令物件中,然後就可以使用這個命令物件,已有的實現完全不用變化。
五、認識命令模式
(1)引數化配置
所謂命令模式的引數化配置,指的是:可以用不同的命令物件,去引數化配置客戶的請求。即:對於Invoker的同一個請求,為其配置不同的Command物件,就會執行不同的功能。
(2)可撤銷的操作
可撤銷操作的意思就是:放棄該操作,回到未執行該操作前的狀態。
有兩種基本的思路來實現可撤銷的操作,一種是補償式,又稱反操作式,比如要撤銷的操作是加的功能,那麼可以用相反的操作即減的功能去實現,撤銷加多少就減多少。
使用命令模式可以實現補償式的可撤銷操作,首先需要把操作過的命令記錄下來,形成命令的歷史列表,撤銷的時候就從最後一個開始執行撤銷。同樣的方式,還可以實現恢復的功能。
另外一種實現可撤銷操作的方式是儲存恢復式,意思就是把操作前的狀態記錄下來,然後要撤銷操作的時候就直接恢復回去就可以了(可以使用備忘錄模式實現)。
(3)巨集命令
什麼是巨集命令呢?簡單點說就是包含多個命令的命令,是一個命令的組合。以去飯店吃飯為例,一般的流程都是:選座位-->點菜-->上菜-->享用美食-->結賬,如果將這幾個步驟中涉及的命令打包一起執行的話,就可以看作是一個巨集命令。
(4)佇列請求
所謂佇列請求,就是對命令物件進行排隊,組成命令佇列,然後依次取出命令物件來執行。還是以去飯店吃飯為例,已經點菜的顧客可能有很多人,點的每一道菜都可以看成是一條命令(需要廚師去做菜,服務員上菜),這些被點的菜就構成了一個命令佇列。廚師一般都會按照點菜的先後順序去做菜,誰的菜先點,就先做誰的。
(5)日誌請求
所謂日誌請求,就是把請求的歷史紀錄儲存下來,一般是採用永久儲存的方式。如果在執行請求的過程中,系統崩潰了,那麼當系統再次執行時,就可以從儲存的歷史記錄中獲取日誌請求,並重新執行命令。
日誌請求的實現有兩種方案:一種是直接使用Java中的序列化方法;另外一種就是在命令物件中新增上儲存和裝載的方法,其實就是讓命令物件自己實現類似序列化的功能。
六、總結
命令模式是對命令的封裝。命令模式把發出命令的責任和執行命令的責任分割開,委派給不同的物件。
每一個命令都是一個操作:請求的一方發出請求要求執行一個操作;接收的一方收到請求,並執行操作。命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的。