一份雞排,要切不要辣。
想像一個熟悉的情況,當我們晚上買宵夜時,先走到鹹酥雞攤,跟老闆說一份雞排,要切不要辣,此時你下了第一個命令。接著走到飲料攤,跟阿姨說一杯紅茶,半糖少冰,此時你下了第二個命令,最後我們開開心心地拿著雞排跟紅茶回家享用。從這個過程中,可以發現我們針對不同的攤位,給予不同命令,我們並不在乎雞排準備的過程,也不在乎是誰來炸雞排,但是我們在意的是拿到切好並且不辣的雞排,紅茶也是這樣的情況。通過這樣的模式,我們可以輕易地傳遞命令(command),客戶端(Client) 不需要理解背後的執行細節,甚至也不需要知道是透過 誰(receiver) 來執行,但是最終我們都能夠得到命令的結果。
在命令模式中,我們想要解決的問題是命令發起者 (client) 及命令接收者 (receiver) 之間的耦合。我們利用一種鬆耦合的模式,當程式需要像某個物件發出請求,不需要知道接收者 (receiver) 是哪個物件,也不必擔心命令的實際執行過程,藉由透過一層抽象的介面來呼叫命令執行。由於執行細節已經被封裝在 command 這個物件中,因此當 command 物件被傳遞時,可以很輕易在任何時刻任何地方去執行真正的命令。下面我們可以實際來看看程式碼的範例。
以下是一個用 Javascript 命令模式的簡單實作,在此只敘述一些基本的情況,foodBooth
定義了真正實作的細節,createFoodCommand
創造出一層抽象的介面,並封裝執行的程序,foodCommand
是一個可易被傳遞的物件,並可藉由呼叫 execute
來得到結果。
從分工的角度來看,如果兩個人(A 及 B)同時開發這個 feature,A 負責實作邏輯,B 負責編排版面,兩人需要約定好的介面就是 foodCommand
中的 execute
, A 只專注實作 execute
的邏輯,並不在乎在哪裡或何時被呼叫。B 只專注綁定 execute
到合適的目標及觸發的時機,並不在乎 execute
內部的實作邏輯。藉由命令模式,我們能夠降低 A 及 B 的耦合程度,進而增加程式的可讀性及維護性。
const foodBooth = {
prepare() {
console.log("炸雞排");
return Promise.resolve(this);
},
cut() {
console.log("剪雞排");
return Promise.resolve(this);
},
putSeasoning() {
console.log("調味");
return Promise.resolve(this);
},
deliver() {
console.log("銀貨兩訖");
return Promise.resolve(this);
}
};
const createFoodCommand = receiver => ({
execute() {
return receiver
.prepare()
.cut()
.putSeasoning()
.deliver();
}
});
const foodCommand = createFoodCommand(foodBooth);
const setCommand = (target, eventType, command) => {
target.addEventListener(eventType, command.execute);
};
const button = document.getElementById("button1");
setCommand(button, "click", foodCommand);
以上是命令模式的基本應用,在進階的應用上,我們還可以將撤回執行結果的方式 (undo) 封裝在 command
當中,藉由執行 undo 來得到上次的結果。也可以實作命令佇列,更彈性的編排命令需求,來擴展更複雜的商業邏輯。
命令模式有效地解耦呼叫者及執行者之間的關係,讓兩者能專注在自己的領域,藉由這個模式,我們可以提高程式碼的品質及專案維護性。
作者:Ron