命令模式是行為型設計模式,本文對命令模式Command進行了簡單介紹,深入的分析了命令模式的意圖,以及演化邏輯,並且給出了命令模式的Java版示例,理解命令模式有利於理解面向對象的編程思想,一切皆是對象,方法調用也是一種對象。 ...
命令模式(Command) 請分析上圖中這條命令的涉及到的角色以及執行過程,一種可能的理解方式是這樣子的: 涉及角色為:大狗子和大狗子他媽 過程為:大狗子他媽角色 調用 大狗子的“回家吃飯”方法
引子
package command.origin; public class BigDog { public void goHomeForDinner() { System.out.println("回家吃飯"); } }
package command.origin; public class BigDogMother { public static void main(String[] args) { BigDog bigDog = new BigDog(); bigDog.goHomeForDinner(); } }BigDog類擁有回家吃飯方法goHomeForDinner BigDogMother作為客戶端調用BigDog的回家吃飯方法,完成了“大狗子回家吃飯”這個請求 上面的示例中,通過對命令執行者的方法調用,完成了命令的下發,命令調用者與命令執行者之間是緊密耦合的 我們是否可以考慮換一種思維方式,將“你媽喊你回家吃飯”這一命令封裝成為一個對象? 不再是大狗子他媽調用大狗子的回家吃飯方法 而是大狗子他媽下發了一個命令,命令的內容是“大狗子回家吃飯” 接下來是命令的執行 這樣的話,“命令”就不再是一種方法調用了,在大狗子媽和大狗子之間多了一個環節---“命令” 看下代碼演變 BigDog 沒有變化 新增加了命令類Command 使用對象的接受者BigDog 進行初始化 命令的execute方法內部調用接受者BigDog的方法 BigDogMother中下發了三個命令 然後逐個執行這三個命令
package command.origin; public class BigDog { public void goHomeForDinner() { System.out.println("回家吃飯"); } }
package command.origin; public class Command { private BigDog bigDog; Command(BigDog bigDog) { this.bigDog = bigDog; } public void execute() { bigDog.goHomeForDinner(); } }
package command.origin; public class BigDogMother { public static void main(String[] args) { BigDog bigDog = new BigDog(); Command command1 = new Command(bigDog); Command command2 = new Command(bigDog); Command command3 = new Command(bigDog); command1.execute(); command2.execute(); command3.execute(); } }從上面的代碼示例中看到,通過對“請求”也就是“方法調用”的封裝,將請求轉變成了一個個的命令對象 命令對象本身內部封裝了一個命令的執行者 好處是:命令可以進行保存傳遞了,命令發出者與命令執行者之間完成瞭解耦,命令發出者甚至不知道具體的執行者到底是誰 而且執行的過程也更加清晰了
意圖
將一個請求封裝為一個對象,從而使可用不同的請求對客戶進行參數化; 對請求排隊或者記錄請求日誌,以及支持可撤銷的操作。 別名 行為Action或者事物Transaction 命令模式就是將方法調用這種命令行為或者說請求 進一步的抽象,封裝為一個對象結構
上面的“大狗子你媽喊你回家吃飯”的例子只是展示了對於“命令”的一個封裝。只是命令模式的一部分。 下麵看下命令模式完整的結構 命令角色Command 聲明瞭一個給所有具體命令類的抽象介面 做為抽象角色,通常是介面或者實現類 具體命令角色ConcreteCommand定義一個接受者和行為之間的弱耦合關係,實現execute()方法
負責調用命令接受者的響相應操作 請求者角色Invoker 負責調用命令對象執行命令,相關的方法叫做行動action方法 接受者角色Receiver 負責具體實施和執行一個請求,任何一個類都可以成為接收者 Command角色封裝了命令接收者並且內部的執行方法調用命令接收者的方法 也就是一般形如: Command(Receiver receiver){ ...... execute(){ receiver.action(); ... 而Invoker角色接收Command,調用Command的execute方法 通過將“命令”這一行為抽象封裝,命令的執行不再是請求者調用被請求者的方法這種強關聯 ,而是可以進行分離 分離後,這一命令就可以像普通的對象一樣進行參數傳遞等
結構代碼示例
command角色package command; public interface Command { void execute(); }
ConcreateCommand角色
內部擁有命令接收者,內部擁有execute方法
package command; public class ConcreateCommand implements Command { private Receiver receiver; ConcreateCommand(Receiver receiver) { this.receiver = receiver; } @Override public void execute() { receiver.action(); } }Receiver命令接收者,實際執行命令的角色
package command; public class Receiver { public void action(){ System.out.println("command receiver do sth...."); } }命令請求角色Invoker 用於處理命令,調用命令角色執行命令
package command; public class Invoker { private Command command; Invoker(Command command){ this.command = command; } void action(){ command.execute(); } }客戶端角色
package command; public class Client { public static void main(String[] args){ Receiver receiver = new Receiver(); Command command = new ConcreateCommand(receiver); Invoker invoker = new Invoker(command); invoker.action(); } }在客戶端角色的測試代碼中,我們創建了一個命令,指定了接收者(實際執行者) 然後將命令傳遞給命令請求調用者 雖然最終命令的接收者為receiver,但是很明顯如果這個Command是作為參數傳遞進來的 Client照樣能夠運行,他只需要藉助於Invoker執行命令即可 命令模式關鍵在於:引入命令類對方法調用這一行為進行封裝 命令類使的命令發送者與接收者解耦,命令請求者通過命令類來執行命令接收者的方法 而不在是直接請求命名接收者
代碼示例
假設電視機只有三個操作:開機open 關機close和換台change channel。 用戶通過遙控器對電視機進行操作。 電視機本身是命令接收者 Receiver 遙控器是請求者角色Invoker 用戶是客戶端角色Client 需要將用戶通過遙控器下發命令的行為抽象為命令類Command Command有開機命令 關機命令和換台命令 命令的執行需要藉助於命令接收者 Invoker 調用Command的開機命令 關機命令和換台命令 電視類 Tvpackage command.tv; public class Tv { public void turnOn(){ System.out.println("打開電視"); } public void turnOff(){ System.out.println("關閉電視"); } public void changeChannel(){ System.out.println("換台了"); } }Command介面
package command.tv; public interface Command { void execute(); }三個具體的命令類 內部都保留著執行者,execute方法調用他們的對應方法
package command.tv; public class OpenCommand implements Command { private Tv myTv; OpenCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.turnOn(); } }
package command.tv; public class CloseCommand implements Command { private Tv myTv; CloseCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.turnOff(); } }
package command.tv; public class ChangeChannelCommand implements Command { private Tv myTv; ChangeChannelCommand(Tv myTv) { this.myTv = myTv; } @Override public void execute() { myTv.changeChannel(); } }遙控器Controller 擁有三個命令
package command.tv; public class Controller { private Command openCommand = null; private Command closeCommand = null; private Command changeChannelCommand = null; public Controller(Command on, Command off, Command change) { openCommand = on; closeCommand = off; changeChannelCommand = change; } public void turnOn() { openCommand.execute(); } public void turnOff() { closeCommand.execute(); } public void changeChannel() { changeChannelCommand.execute(); } }用戶類User
package command.tv; public class User { public static void main(String[] args) { Tv myTv = new Tv(); OpenCommand openCommand = new OpenCommand(myTv); CloseCommand closeCommand = new CloseCommand(myTv); ChangeChannelCommand changeChannelCommand = new ChangeChannelCommand(myTv); Controller controller = new Controller(openCommand, closeCommand, changeChannelCommand); controller.turnOn(); controller.turnOff(); controller.changeChannel(); } }
以上示例將電視機的三種功能開機、關機、換台 抽象為三種命令 一個遙控器在初始化之後,就可以擁有開機、關機、換台的功能,但是卻完全不知道底層的實際工作的電視。
命令請求記錄
一旦將“發起請求”這一行為進行抽象封裝為命令對象 那麼“命令”也就具有了一般對象的基本特性,比如,作為參數傳遞 比如使用容器存放進行存放 比如定義一個ArrayList 用於保存命令 ArrayList<Command> commands = new ArrayList<Command>(); 這就形成了一個隊列 你可以動態的向隊列中增加命令,也可以從隊列中移除命令 你還可以將這個隊列保存起來,批處理的執行或者定時每天的去執行 你還可以將這些命令請求持久化到文件中,因為這些命令、請求 也不過就是一個個的對象而已請求命令隊列
既然可以使用容器存放命令對象,我們可以實現一個命令隊列,對命令進行批處理 新增加一個CommandQueue類,內部使用ArrayList存儲命令 execute()方法,將內部的請求命令隊列全部執行package command; import java.util.ArrayList; public class CommandQueue { private ArrayList<Command> commands = new ArrayList<Command>(); public void addCommand(Command command) { commands.add(command); } public void removeCommand(Command command) { commands.remove(command); } //執行隊列內所有命令 public void execute() { for (Object command : commands) { ((Command) command).execute(); } } }同時調整Invoker角色,使之可以獲得請求命令隊列,並且執行命令請求隊列的方法
package command; public class Invoker { private Command command; Invoker(Command command) { this.command = command; } void action() { command.execute(); } //新增加命令隊列 private CommandQueue commandQueue; public Invoker(CommandQueue commandQueue) { this.commandQueue = commandQueue; } /* * 新增加隊列批處理方法*/ public void batchAction() { commandQueue.execute(); } }從上面的示意代碼可以看得出來,請求隊列的關鍵就是命令類 一旦創建了命令類,就解除了命令請求者與命令接收者之間耦合,就可以把命令當做一個普通對象進行處理,調用他們的execute()執行方法 所謂請求隊列不就是使用容器把命令對象保存起來,然後調用他們的execute方法嘛 所以說,命令請求的對象化,可以實現對請求排隊或者記錄請求日誌的目的,就是命令對象的隊列