Command 命令模式

当一个请求 (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

优点与缺点

命令模式的优点在於,我们「接收请求」、「执行请求」两个不同的关注点分离。而当我们有专门负责「接收请求」的角色出现之後,就可以在实际执行之前,预先做不同的处理,譬如

  • 安排请求处理的行程
  • 决定接受或拒绝请求
  • 决定重做或撤销请求

不过当然缺点就是,程序码会比原本的复杂许多罗


<<:  Day 30. 监控大挑战 - 以 Zabbix 为例 - 完赛

>>:  Day 29 使用 docker-compose 来安装 Wordpress

Day28. Rails 搭配 DataTable 写出完美的列表页

今天要讲Stimulus & Datatable 的用法,不过不会Stimulus的读者们不...

[Day08]稽核行程倒数准备

依不同单位的规定,在时程上会有些差异,不过应该大同小异。 流程大部份是确认稽核阶段 → 来准备稽核...

Day 02 : 用於生产的机械学习 ML in Production

ML 就像孩子一样,孩提时百般呵护,长大时不得不面对外界的残酷。布署到商务情境的 ML 模型,某方面...

Unity与Photon的新手相遇旅途 | Day14-生成敌人

今天介绍的内容为如何固定位置生成以及随机位置生成敌人。 ...

【DAY 11】SharePoint 後记- 为什麽要选择 SharePoint?

哈罗大家好~ 关於 SharePoint 的应用,到昨天告一段落,回顾一下你可能会觉得,文件库、清单...