好傢伙,
0.什麼是命令模式
在軟體系統中,“行為請求者”與“行為實現者”通常呈現一種“緊耦合”。
但在某些場合,比如要對行為進行“記錄、撤銷/重做、事務”等處理,這種無法抵禦變化的緊耦合是不合適的。
在這種情況下,如何將“行為請求者”與“行為實現者”解耦?將一組行為抽象為物件,實現二者之間的松耦合。這就是命令模式(Command Pattern)。
0.1.角色解釋
Command(定義介面): 定義命令的介面,宣告執行的方法。
ConcreteCommand(實現介面):命令介面實現物件,是“虛”的實現;通常會持有接收者,並呼叫接收者的功能來完成命令要執行的操作。
Receiver(接收者):接收者,真正執行命令的物件。任何類都可能成為一個接收者,只要它能夠實現命令要求實現的相應功能。
Invoker(呼叫者):要求命令物件執行請求,通常會持有命令物件,可以持有很多的命令物件。
這個是客戶端真正觸發命令並要求命令執行相應操作的地方,也就是說相當於使用命令物件的入口。
Client(具體命令):建立具體的命令物件,並且設定命令物件的接收者。注意這個不是我們常規意義上的客戶端,而是在組裝命令物件和接收者,或許,
把這個Client稱為裝配者會更好理解,因為真正使用命令的客戶端是從Invoker來觸發執行
0.2.模式分析
0.3.模式優點
0.4.缺點
1.例項
想象一個例項
餐廳點餐案例
現在我走進一家餐廳,向一名服務員點餐,點了一份牛肉漢堡,
服務員將這份”牛肉漢堡"訂單交給廚師,廚師製作這份牛肉牛肉漢堡
來個程式碼實現
// 廚師類
class Chef {
makeBurger() {
console.log("This is your burger.");
}
}
// 服務員類
class Waitress {
constructor(chef) {
this.chef = chef;
}
takeOrder(order) {
// 服務員接收訂單並處理
console.log("Waitress is taking the order for a " + order);
this.chef.makeBurger(); // 直接通知廚師製作漢堡
}
}
// 客戶端程式碼
(() => {
const chef = new Chef();
const waitress = new Waitress(chef);
// 客戶點餐 服務員通知
waitress.takeOrder("burger");
})();
那麼如果我使用命令模式去實現呢?
// 命令介面(抽象概念,使用ES6的類來表示)
class Command {
execute() {
// 抽象方法execute,將在具體命令中實現
throw new Error('You should implement the execute method');
}
}
// 具體命令類
class OrderBurgerCommand extends Command {
constructor(receiver) {
super();
this.receiver = receiver;
}
execute() {
this.receiver.makeBurger();
}
}
// 接收者類
class Chef {
makeBurger() {
console.log("Chef is making a burger.");
}
}
// 請求者類
class Waitress {
constructor() {
this.commands = [];
}
takeOrder(command) {
if (!(command instanceof Command)) {
throw new Error('You can only take order of Command instances');
}
this.commands.push(command);
}
notify() {
this.commands.forEach(command => command.execute());
}
}
// 客戶類
class Customer {
constructor() {
this.waitress = new Waitress();
this.chef = new Chef();
this.burgerCommand = new OrderBurgerCommand(this.chef);
}
orderBurger() {
this.waitress.takeOrder(this.burgerCommand);
}
serveOrder() {
this.waitress.notify();
}
}
// 客戶端程式碼
(() => {
const customer = new Customer();
customer.orderBurger(); // 客戶點餐
customer.serveOrder(); // 服務員通知廚師製作漢堡
})();
2.增加需求
同樣的,如果我們新下單一條“薯條”
使用命令模式
// 命令模式實現
// 命令介面
class Command {
execute() {
// 抽象方法,需要在子類中實現
}
}
// 漢堡命令
class BurgerCommand extends Command {
constructor(chef) {
super();
this.chef = chef;
}
execute() {
this.chef.makeBurger();
}
}
// 薯條命令
class FrenchFriesCommand extends Command {
constructor(chef) {
super();
this.chef = chef;
}
execute() {
this.chef.makeFrenchFries();
}
}
// 廚師類
class Chef {
makeBurger() {
console.log('製作漢堡');
}
makeFrenchFries() {
console.log('製作薯條');
}
}
// 服務員類
class Waiter {
constructor() {
this.commands = [];
}
order(command) {
this.commands.push(command);
console.log('訂單已接收');
}
serve() {
this.commands.forEach(command => command.execute());
}
}
// 客戶類
class Customer {
constructor(waiter) {
this.waiter = waiter;
}
orderBurger() {
const chef = new Chef();
const burgerCommand = new BurgerCommand(chef);
this.waiter.order(burgerCommand);
}
orderFrenchFries() {
const chef = new Chef();
const friesCommand = new FrenchFriesCommand(chef);
this.waiter.order(friesCommand);
}
}
// 客戶端程式碼
(() => {
const waiter = new Waiter();
const customer = new Customer(waiter);
// 客戶點一份漢堡
customer.orderBurger();
// 客戶再點一份薯條
customer.orderFrenchFries();
// 服務員開始服務
waiter.serve();
})();
再結合以上程式碼來看,命令模式的優勢有
1.真正實現瞭解耦:客戶不需要知道漢堡製作的細節,服務員也不需要知道漢堡製作的細節,客戶僅僅是下單,服務員僅僅是通知
2.易於擴充套件:如果需要新增新的操作(如包裝、加熱),可以建立新的命令類,而無需修改現有的類結構。
透過漢堡和薯條的例子,我們可以看到命令模式如何使得程式碼更加靈活、可維護,並且更容易進行擴充套件。