公告
如果您是第一次閱讀我的設(shè)計模式系列文章罗捎,建議先閱讀設(shè)計模式開篇祠汇,希望能得到您寶貴的建議。
前言
隨著上文 裝飾器模式 中的顧客Alice
購買了機器人回家后畴博,他開始了機器人使用之旅瓢捉。
正文
機器人
:“你好频丘,Alice!”
Alice
:“你好!你叫什么泡态?”
機器人
:“Alice搂漠,我是你忠臣的仆人 Samu∈蘖蓿”
Alice
:“Samu状答,你有什么功能冷守?”
機器人
:“我會唱歌刀崖,我也會跳舞。Alice拍摇,你只要說 唱歌+歌曲名稱
或者跳舞+舞蹈名稱
我就會唱歌跳舞亮钦。如果你連續(xù)的說唱歌+歌曲名稱
跳舞+舞蹈名稱
,我會一邊唱歌一邊跳舞充活》淅颍”
Alice
:“唱歌稻香 跳舞肚皮舞”
機器人
:“對這個世界如果你有太多的抱怨”
機器人
:“跌倒了 就不敢繼續(xù)往前走”
機器人
:“為什么 人要這么的脆弱 墮落”
……(畫外音:曼妙的舞姿)……
程序員視角
現(xiàn)在要實現(xiàn)對機器人Samu
說(發(fā)出指令) 唱歌+歌曲名稱
或者跳舞+舞蹈名稱
,機器人便會自動的唱歌或跳舞混卵。
代碼實現(xiàn)
定義命令的接口的目的是為了抽象類型映穗,并且將命令實現(xiàn)分離。
public interface ICommand {
void excute();
}
內(nèi)部命令抽象對象幕随,用于提供命令的上下文蚁滋。
public abstract class Command implements ICommand {
private String param;
public Command(String param) {
this.param = param;
}
protected String param() {
return param;
}
}
唱歌命令的實現(xiàn)(跳舞命令實現(xiàn)類似)
public class SongCommandImpl extends Command {
public static final String KEY_SONG = "唱歌";
public SongCommandImpl(String param) {
super(param);
}
@Override
public void excute() {
System.out.println("調(diào)用指令 " + KEY_SONG + param());
}
}
命令的接口與實現(xiàn)均已準備妥當,接下去是思考如何調(diào)用命令赘淮。
為了避免客戶端與具體的命令對象耦合辕录,所以通常建議搭配適配器模式
將唱歌
、跳舞
這些指令梢卸,轉(zhuǎn)化為程序可理解的SongCommandImpl
走诞、DanceCommandImpl
。
// 適配器對象用于適配字符串到命令的執(zhí)行接口
public class StringCommandAdapter implements ICommand {
private String method;
private String param;
private HashMap<String, Command> map = new HashMap<>();
private Command pickCommand(String method, String param) {
Command command = null;
if (method.startsWith(DanceCommandImpl.KEY_DANCE)) {
command = createCommand(DanceCommandImpl.KEY_DANCE, param, DanceCommandImpl.class);
} else if (method.startsWith(SongCommandImpl.KEY_SONG)) {
command = createCommand(SongCommandImpl.KEY_SONG, param, SongCommandImpl.class);
}
return command;
}
private Command createCommand(String key, String param, Class<?> clazz) {
if (map.containsKey(key)) {
return map.get(key);
}
Command command = null;
try {
Constructor<?> constructor = clazz.getConstructor(String.class);
command = (Command) constructor.newInstance(param);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
if (command != null) {
map.put(key, command);
return command;
} else {
throw new IllegalArgumentException("NO COMMAND CREATED!!!");
}
}
public StringCommandAdapter(String method, String param) {
this.method = method;
this.param = param;
}
@Override
public void excute() {
System.out.println("調(diào)用指令:" + toString());
Command command = pickCommand(this.method, this.param);
command.excute();
}
@Override
public String toString() {
return "StringCommandAdapter{" +
"method='" + method + '\'' +
", param='" + param + '\'' +
", map=" + map +
'}';
}
}
命令如何構(gòu)建已經(jīng)完畢蛤高,接著是命令如何被觸發(fā)蚣旱。這里模擬構(gòu)建機器人接收到命令在觸發(fā)
// 構(gòu)建命令管理器碑幅,命令的日志跟蹤都可以在這里實現(xiàn)。
public class CommandManager {
public void invoke(StringCommandAdapter adapter) {
adapter.excute();
}
}
考慮到通常命令都是通過觀察者接收到消息后才觸發(fā)調(diào)用的塞绿,所以這里模擬了觀察者接收到消息的調(diào)用過程枕赵。
public class SamuCommandReceiver {
private CommandManager invoke = new CommandManager();
private Machine machine;
public SamuCommandReceiver(Machine machine) {
this.machine = machine;
System.out.printf("機器人%s的接收功能正常開啟%n", machine);
}
public void onReceive(String command, String param) {
System.out.printf("機器人%s接收到指令:%s,%s%n", machine, command, param);
invoke.invoke(new StringCommandAdapter(command, param));
}
}
客戶端的調(diào)用
public static void main(String args[]) {
Machine machine = new Machine("Samu");
SamuCommandReceiver receiver = new SamuCommandReceiver(machine);
receiver.onReceive("唱歌", "稻香");
receiver.onReceive("跳舞", "肚皮舞");
}
運行結(jié)果
創(chuàng)建了機器人 Samu
機器人Samu的接收功能正常開啟
機器人Samu接收到指令:唱歌位隶,稻香
調(diào)用指令:StringCommandAdapter{method='唱歌', param='稻香', map={}}
調(diào)用指令 唱歌稻香
機器人Samu接收到指令:跳舞拷窜,肚皮舞
調(diào)用指令:StringCommandAdapter{method='跳舞', param='肚皮舞', map={}}
調(diào)用指令 跳舞肚皮舞
總結(jié)
在軟件設(shè)計中,我們經(jīng)常需要向某些對象發(fā)送請求涧黄,但是并不知道請求的接收者是誰篮昧,也不知道被請求的操作是哪個,我們只需在程序運行時指定具體的請求接收者即可笋妥,此時懊昨,可以使用命令模式來進行設(shè)計,使得請求發(fā)送者與請求接收者消除彼此之間的耦合春宣,讓對象之間的調(diào)用關(guān)系更加靈活酵颁。
命令模式可以對發(fā)送者和接收者完全解耦,發(fā)送者與接收者之間沒有直接引用關(guān)系月帝,發(fā)送請求的對象只需要知道如何發(fā)送請求躏惋,而不必知道如何完成請求。這就是命令模式的模式動機嚷辅。
在命令模式中簿姨,將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進行參數(shù)化簸搞;對請求排隊或者記錄請求日志扁位,以及支持可撤銷的操作。命令模式是一種對象行為型模式趁俊,其別名為動作模式或事務(wù)模式域仇。
命令模式包含四個角色:
- 抽象命令類中聲明了用于執(zhí)行請求的execute()等方法,通過這些方法可以調(diào)用請求接收者的相關(guān)操作寺擂;
- 具體命令類是抽象命令類的子類暇务,實現(xiàn)了在抽象命令類中聲明的方法,它對應(yīng)具體的接收者對象沽讹,將接收者對象的動作綁定其中般卑;
- 調(diào)用者即請求的發(fā)送者,又稱為請求者爽雄,它通過命令對象來執(zhí)行請求蝠检;
- 接收者執(zhí)行與請求相關(guān)的操作,它具體實現(xiàn)對請求的業(yè)務(wù)處理挚瘟。
命令模式的本質(zhì)是對命令進行封裝叹谁,將發(fā)出命令的責任和執(zhí)行命令的責任分割開饲梭。
命令模式使請求本身成為一個對象,這個對象和其他對象一樣可以被存儲和傳遞焰檩。
命令模式的主要優(yōu)點在于降低系統(tǒng)的耦合度憔涉,增加新的命令很方便,而且可以比較容易地設(shè)計一個命令隊列和宏命令析苫,并方便地實現(xiàn)對請求的撤銷和恢復兜叨;
其主要缺點在于可能會導致某些系統(tǒng)有過多的具體命令類。
命令模式適用情況包括:
- 需要將請求調(diào)用者和請求接收者解耦衩侥,使得調(diào)用者和接收者不直接交互国旷;
- 需要在不同的時間指定請求、將請求排隊和執(zhí)行請求茫死;
- 需要支持命令的撤銷操作和恢復操作跪但,需要將一組操作組合在一起,即支持宏命令峦萎。