命令模式 Command 行為型 設計模式(十八)
阿新 • • 發佈:2018-12-11
命令模式(Command)
請分析上圖中這條命令的涉及到的角色以及執行過程,一種可能的理解方式是這樣子的:
涉及角色為:大狗子和大狗子他媽
過程為:大狗子他媽角色
呼叫 大狗子的“回家吃飯”方法
不再是大狗子他媽呼叫大狗子的回家吃飯方法
而是大狗子他媽下發了一個命令,命令的內容是“大狗子回家吃飯”
接下來是命令的執行
這樣的話,“命令”就不再是一種方法呼叫了,在大狗子媽和大狗子之間多了一個環節---“命令”
看下程式碼演變
BigDog 沒有變化
新增加了命令類Command 使用物件的接受者BigDog 進行初始化
命令的execute方法內部呼叫接受者BigDog的方法
BigDogMother中下發了三個命令
然後逐個執行這三個命令
定義一個接受者和行為之間的弱耦合關係,實現execute()方法
負責呼叫命令接受者的響相應操作 請求者角色Invoker 負責呼叫命令物件執行命令,相關的方法叫做行動action方法 接受者角色Receiver 負責具體實施和執行一個請求,任何一個類都可以成為接收者 Command角色封裝了命令接收者並且內部的執行方法呼叫命令接收者的方法 也就是一般形如: Command(Receiver receiver){ ...... execute(){ receiver.action(); ... 而Invoker角色接收Command,呼叫Command的execute方法 通過將“命令”這一行為抽象封裝,命令的執行不再是請求者呼叫被請求者的方法這種強關聯 ,而是可以進行分離 分離後,這一命令就可以像普通的物件一樣進行引數傳遞等
引子
package command.origin; public class BigDog { public void goHomeForDinner() { System.out.println("回家吃飯"); } }
package command.origin; public class BigDogMother {BigDog類擁有回家吃飯方法goHomeForDinner BigDogMother作為客戶端呼叫BigDog的回家吃飯方法,完成了“大狗子回家吃飯”這個請求 上面的示例中, 通過對命令執行者的方法呼叫,完成了命令的下發, 命令呼叫者與命令執行者之間是緊密耦合的 我們 是否可以考慮換一種思維方式,將“你媽喊你回家吃飯”這一命令封裝成為一個物件?public static void main(String[] args) { BigDog bigDog = new BigDog(); bigDog.goHomeForDinner(); } }
packagecommand.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方法嘛 所以說, 命令請求的物件化,可以實現對請求排隊或者記錄請求日誌的目的,就是命令物件的佇列