公告
如果您是第一次閱讀我的設計模式系列文章,建議先閱讀設計模式開篇,希望能得到您寶貴的建議。
前言
隨著上文 裝飾器模式 中的顧客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("調用指令 " + KEY_SONG + param());
}
}
命令的接口與實現(xià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("調用指令:" + toString());
Command command = pickCommand(this.method, this.param);
command.excute();
}
@Override
public String toString() {
return "StringCommandAdapter{" +
"method='" + method + '\'' +
", param='" + param + '\'' +
", map=" + map +
'}';
}
}
命令如何構建已經(jīng)完畢,接著是命令如何被觸發(fā)。這里模擬構建機器人接收到命令在觸發(fā)
// 構建命令管理器,命令的日志跟蹤都可以在這里實現(xiàn)。
public class CommandManager {
public void invoke(StringCommandAdapter adapter) {
adapter.excute();
}
}
考慮到通常命令都是通過觀察者接收到消息后才觸發(fā)調用的,所以這里模擬了觀察者接收到消息的調用過程。
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));
}
}
客戶端的調用
public static void main(String args[]) {
Machine machine = new Machine("Samu");
SamuCommandReceiver receiver = new SamuCommandReceiver(machine);
receiver.onReceive("唱歌", "稻香");
receiver.onReceive("跳舞", "肚皮舞");
}
運行結果
創(chuàng)建了機器人 Samu
機器人Samu的接收功能正常開啟
機器人Samu接收到指令:唱歌,稻香
調用指令:StringCommandAdapter{method='唱歌', param='稻香', map={}}
調用指令 唱歌稻香
機器人Samu接收到指令:跳舞,肚皮舞
調用指令:StringCommandAdapter{method='跳舞', param='肚皮舞', map={}}
調用指令 跳舞肚皮舞
總結
在軟件設計中,我們經(jīng)常需要向某些對象發(fā)送請求,但是并不知道請求的接收者是誰,也不知道被請求的操作是哪個,我們只需在程序運行時指定具體的請求接收者即可,此時,可以使用命令模式來進行設計,使得請求發(fā)送者與請求接收者消除彼此之間的耦合,讓對象之間的調用關系更加靈活。
命令模式可以對發(fā)送者和接收者完全解耦,發(fā)送者與接收者之間沒有直接引用關系,發(fā)送請求的對象只需要知道如何發(fā)送請求,而不必知道如何完成請求。這就是命令模式的模式動機。

在命令模式中,將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進行參數(shù)化;對請求排隊或者記錄請求日志,以及支持可撤銷的操作。命令模式是一種對象行為型模式,其別名為動作模式或事務模式。
命令模式包含四個角色:
- 抽象命令類中聲明了用于執(zhí)行請求的execute()等方法,通過這些方法可以調用請求接收者的相關操作;
- 具體命令類是抽象命令類的子類,實現(xiàn)了在抽象命令類中聲明的方法,它對應具體的接收者對象,將接收者對象的動作綁定其中;
- 調用者即請求的發(fā)送者,又稱為請求者,它通過命令對象來執(zhí)行請求;
- 接收者執(zhí)行與請求相關的操作,它具體實現(xiàn)對請求的業(yè)務處理。
命令模式的本質是對命令進行封裝,將發(fā)出命令的責任和執(zhí)行命令的責任分割開。
命令模式使請求本身成為一個對象,這個對象和其他對象一樣可以被存儲和傳遞。
命令模式的主要優(yōu)點在于降低系統(tǒng)的耦合度,增加新的命令很方便,而且可以比較容易地設計一個命令隊列和宏命令,并方便地實現(xiàn)對請求的撤銷和恢復;
其主要缺點在于可能會導致某些系統(tǒng)有過多的具體命令類。
命令模式適用情況包括:
- 需要將請求調用者和請求接收者解耦,使得調用者和接收者不直接交互;
- 需要在不同的時間指定請求、將請求排隊和執(zhí)行請求;
- 需要支持命令的撤銷操作和恢復操作,需要將一組操作組合在一起,即支持宏命令。