先上headfirst的類圖和代碼:
- 具體接收者類:
public class TV {
public void on(){}
public void off(){}
public void setInputChannel(){}
public void setVolume(){}
}
public class CeilingFan {
public void high(){}
public void medium(){}
public void low(){}
public void off(){}
public void getSpeed(){}
}
public class OutDoorLight {
public void on(){}
public void off(){}
}
- 命令類
public interface Command {
public void execute();
public void undo();
}
public class LightOnCommand implements Command {
Light light;
public LightOnCommand(Light light){
this.light = light;
}
@Override
public void execute() {
light.on();
}
@Override
public void undo() {
light.off();
}
}
- 控制器類
public class RemoteControl {
Command[] onCommands;
Command[] offCommands;
// 前一個(gè)命令將被記錄在這里
Command undoCommand;
public RemoteControl() {
onCommands = new Command[7];
offCommands = new Command[7];
Command noCommand = new NoCommand();
for (int i = 0; i < 7; i++) {
onCommands[i] = noCommand;
offCommands[i] = noCommand;
}
// 一開始并沒(méi)有所謂的“前一個(gè)命令”,所以將它設(shè)置成NoCommand
undoCommand = noCommand;
}
public void setCommand(int slot, Command onCommand, Command offCommand) {
onCommands[slot] = onCommand;
offCommands[slot] = offCommand;
}
// 當(dāng)按下按鈕,我們?nèi)〉眠@個(gè)命令,并優(yōu)先執(zhí)行它,然后將它記錄在undoCommand中。
public void onButtonWasPushed(int slot){
onCommands[slot].execute();
undoCommand = onCommands[slot];
}
public void offButtonWasPushed(int slot){
offCommands[slot].execute();
undoCommand = offCommands[slot];
}
// 當(dāng)按下撤銷按鈕,我們調(diào)用undoCommand實(shí)例變量的undo()方法,就可以倒轉(zhuǎn)前一個(gè)命令
public void undoButtonWasPushed(){
undoCommand.undo();
}
}
- 客戶端類,略
如果不使用命令模式
public class RemoteControl {
TV tv = new TV();
... //創(chuàng)建一堆實(shí)際執(zhí)行操作的類
public void onButtonWasPushed(int slot){
if(slot == 1){
tv.on();
}else if() //....一堆if else判斷
}
}
1.RemoteControl直接依賴所有具體執(zhí)行類,高度耦合。
2.如果現(xiàn)在需要新增或者刪除一種操作,必須修改RemoteControl,并在if else中添加或刪除一條分支,很難擴(kuò)展
3.每個(gè)遙控器插槽和具體操作已經(jīng)綁死,無(wú)法在運(yùn)行時(shí)變更。
4.要實(shí)現(xiàn)undo不是很優(yōu)雅,雖然也可以存儲(chǔ)最后一次按鍵的slot和按鍵類型(on or off)來(lái)進(jìn)行undo,代碼將會(huì)又臭又長(zhǎng),極難維護(hù)(因?yàn)橛械谋热绲跎人霓D(zhuǎn)速有五檔。。。)
5.要實(shí)現(xiàn)多種操作組合也比較困難,能想到的方式是:媽的,想不到啥能說(shuō)出口的。。。
命令模式到底怎么牛逼了
- 從最前面的客戶(披薩店顧客)到命令控制者(服務(wù)員)到命令實(shí)際執(zhí)行者(廚師),他們相互之間從來(lái)不直接說(shuō)出自己的需求,從頭至尾都是以命令對(duì)象(訂單)說(shuō)話,所以一個(gè)訂單將他們的相互依賴完全解耦。
- 上面的例子中命令可能是一個(gè)一個(gè)單獨(dú)存在并永久可以重復(fù)使用的,也可以實(shí)現(xiàn)以隊(duì)列來(lái)存儲(chǔ)命令,命令控制者一個(gè)個(gè)取出來(lái)執(zhí)行。
- 寫一個(gè)通用的組合式命令類,就可以無(wú)限復(fù)用實(shí)現(xiàn)任意組合的命令.
public class MultiCommand implements Command {
private Command[] commands;
public MultiCommand(Command[] commands){
this.commands = commands;
}
@Override
public void execute() {
for(Command command : commands){
command.execute();
}
}
@Override
public void undo() {
for(Command command : commands){
command.undo();
}
}
}
- 將一系列操作抽象成命令對(duì)象后,他能被序列化,存到日志,數(shù)據(jù)庫(kù)等地方,當(dāng)系統(tǒng)出現(xiàn)問(wèn)題時(shí),可以將命令列表拉出來(lái)進(jìn)行undo或redo,數(shù)據(jù)庫(kù)事務(wù)等。如果不用命令,每一次操作就必須保存一次原始數(shù)據(jù)快照,空間將會(huì)無(wú)比的浪費(fèi)。
一些問(wèn)題
命令對(duì)象中直接實(shí)現(xiàn)業(yè)務(wù)邏輯不行嗎,為什么需要一個(gè)接受者?其實(shí)這樣實(shí)現(xiàn)也可以,解耦成都就不如把接受者提出來(lái)了,而且以組合接收者的方式實(shí)現(xiàn),運(yùn)行時(shí)也是可以進(jìn)行動(dòng)態(tài)改變的。
多層次的undo操作如何實(shí)現(xiàn)?將只記錄最后一次操作的command改為使用棧實(shí)現(xiàn),每次undo從棧頂彈出一個(gè)元素執(zhí)行undo操作即可,但是有個(gè)問(wèn)題是需要控制棧的大小,無(wú)限制的往棧里添加元素一定會(huì)有問(wèn)題。
宏命令方式實(shí)現(xiàn)功能組合,為什么用多個(gè)command組合,不直接調(diào)用具體執(zhí)行類實(shí)現(xiàn)各自的方法?擴(kuò)展性太差,這樣做就相當(dāng)于寫死了,如果有另外一種組合的需求就必須新增一個(gè)類來(lái)實(shí)現(xiàn),command組合以command數(shù)組傳入的形式能應(yīng)付任意的組合場(chǎng)景而不用修改現(xiàn)有代碼。
headfirst以及各種設(shè)計(jì)模式書籍中的例子中,reciver類執(zhí)行方法都是無(wú)參的,所以實(shí)現(xiàn)命令模式特別精簡(jiǎn)清潔,如果每一個(gè)reciver類的參數(shù)不一樣該怎么辦?首先,不能將傳參這個(gè)動(dòng)作設(shè)計(jì)在execute方法里,設(shè)計(jì)在execute方法里會(huì)導(dǎo)致下面的場(chǎng)景出現(xiàn)
public interface ICommand<T> {
void execute(T parameter);
}
//或者
public interface ICommand<T> {
void execute(Object... parameter);
}
這樣一來(lái)invoke類就必須了解每個(gè)command的參數(shù)類型等邏輯,所謂的命令模式解耦就完全被破壞了,所以應(yīng)該將參數(shù)放在每個(gè)command內(nèi)部,通過(guò)構(gòu)造器或者setter方式設(shè)值,設(shè)值動(dòng)作則由client類進(jìn)行。
public class ParamterCommand implements Command {
private TV tv;
private long time; //燈開多久,參數(shù)通過(guò)構(gòu)造器注入
public MultiCommand(TV tv, long time){
this.tv = tv;
}
@Override
public void execute() {
tv.on(this.time);
}
@Override
public void undo() {
//....
}
}
- 如果命令需要有返回值呢,畢竟head first書中的點(diǎn)餐例子中,客人下了單了,服務(wù)員通知廚師了,廚師也按照菜單做菜了,那做完了總得給客人吃吧? 有返回值就必須在execute方法上返回了,如果返回值能抽象化(返回值都屬于同一種類型),直接把
void execute();
改為AbstractResult execute();
即可。不能抽象調(diào)用者和接受者之間就存在比較大的耦合了,調(diào)用者需要知道接收者的具體返回類型。----------------------其實(shí)命令模式更多的用處在于調(diào)用者只需要發(fā)出命令,調(diào)用者不知道具體執(zhí)行者什么時(shí)候怎么執(zhí)行,所以對(duì)于返回值也應(yīng)該是這樣的:廚師做好東西后繼續(xù)棄用另外一套命令模式,之前階段的命令模式傳的是菜單,當(dāng)前啟用的命令模式傳遞的是做好的一堆菜,甚至可以做好一盤上一盤。當(dāng)然也可以考慮使用觀察者模式來(lái)進(jìn)行后續(xù)上菜操作。
命令模式使用場(chǎng)景
- 遙控器例子(經(jīng)典實(shí)現(xiàn))
- 日志請(qǐng)求
- 工作隊(duì)列
and so on..........