當一個請求 (request) 進入系統之後,通常我們就會立即的處理它。但如果我們不想這麼直接的去處理這些請求,而是先讓這些需求排隊、依序進入,甚至做一些預先處理,可以怎麼做呢?
在命令模式當中,我們將請求本身封裝成一個物件,並且將「接受請求」和「執行請求」兩件事情分開處理。
如果以現實生活中的例子來看,在比較有一點規模的餐廳當中,「處理客人點餐」和「實際準備餐點」是不同的人,譬如服務生 (server) 以及廚師 (chef)
在下面我們就以餐廳來當例子,這裡分別有
Order
介面:當中包含了 execute
方法,以及其他相關資訊,譬如餐點內容與指定廚師AppetizerOrder
, MainCourseOrder
, DessertOrder
類別:分別將客戶點餐的需求包裝成物件Chef
類別:實際執行需求(準備餐點)的角色Server
類別:處理請求(處理點餐)的角色,過程中可以視情況拒絕某些請求程式碼範例如下:
interface Order {
chef: Chef
food: string
execute(): void;
}
class AppetizerOrder implements Order {
chef: Chef
food: string
constructor(chef: Chef, appetizer: string) {
this.chef = chef
this.food = appetizer
}
execute(): void {
this.chef.prepareAppetizer(this.food)
}
}
class MainCourseOrder implements Order {
chef: Chef
food: string
constructor(chef: Chef, mainCourse: string) {
this.chef = chef
this.food = mainCourse
}
execute(): void {
this.chef.prepareMainCourse(this.food)
}
}
class DessertOrder implements Order {
chef: Chef
food: string
constructor(chef: Chef, dessert: string) {
this.chef = chef
this.food = dessert
}
execute(): void {
this.chef.prepareDessert(this.food)
}
}
class Chef {
name: string
constructor(name: string){
this.name = name
}
prepareAppetizer(appetizer: string): void {
console.log(`Chef ${this.name} is preparing appetizer ${appetizer}`)
}
prepareMainCourse(mainCourse: string): void {
console.log(`Chef ${this.name} is preparing main course ${mainCourse}`)
}
prepareDessert(dessert: string): void {
console.log(`Chef ${this.name} is preparing dessert ${dessert}`)
}
}
class Server {
private orders: Order[] = []
private chefs: { [key: string]: number | any } = {}
constructor() {}
addOrders(orders: Order[]): void {
orders.forEach(order => {
const { chef } = order
if (this.chefs[chef.name] >= 3) {
return console.log(`Sorry, chef ${chef.name} is too busy to take ${order.food} order`)
} else {
this.chefs[chef.name] = this.chefs[chef.name] ? this.chefs[chef.name] + 1 : 1
this.orders.push(order)
}
})
}
execute(): void {
this.orders.forEach(order => order.execute())
}
}
介面和類別都建立好了之後,就讓我們來實際開一間餐廳吧!首先這裡有一位服務人員,和兩位廚師
const server = new Server()
const chefGordan = new Chef('gordan')
const chefRamsay = new Chef('ramsay')
接著,餐廳開門之後,陸陸續續有下面七個訂單(請求)出現
const order1 = new AppetizerOrder(chefGordan, 'salad')
const order2 = new AppetizerOrder(chefRamsay, 'edamame')
const order3 = new MainCourseOrder(chefRamsay, 'steak')
const order4 = new MainCourseOrder(chefRamsay, 'duck')
const order5 = new MainCourseOrder(chefRamsay, 'chicken')
const order6 = new DessertOrder(chefGordan, 'cake')
const order7 = new DessertOrder(chefRamsay, 'ice cream')
最後,服務人員收集完訂單之後,就可以讓廚師們開始執行任務
server.addOrders([order1, order2, order3, order4, order5, order6, order7])
server.execute()
不過為了不讓廚師太過操勞,因此服務人員會追蹤廚師目前的工作量,如果超過一定數量,就會直接拒絕客人,不會讓這個請求進入到廚房去
所以最後的得到的結果會是
Sorry, chef ramsay is too busy to take chicken order
Sorry, chef ramsay is too busy to take ice cream order
Chef gordan is preparing appetizer salad
Chef ramsay is preparing appetizer edamame
Chef ramsay is preparing main course steak
Chef ramsay is preparing main course duck
Chef gordan is preparing dessert cake
命令模式的優點在於,我們「接收請求」、「執行請求」兩個不同的關注點分離。而當我們有專門負責「接收請求」的角色出現之後,就可以在實際執行之前,預先做不同的處理,譬如
不過當然缺點就是,程式碼會比原本的複雜許多囉