Skip to content

命令模式

命令模式通常包含以下几个角色:

  • Command(命令接口):声明执行操作的接口
  • ConcreteCommand(具体命令):实现命令接口,绑定接收者
  • Receiver(接收者):执行具体操作的对象
  • Invoker(调用者):持有命令对象并调用其执行方法
  • Client(客户端):创建具体命令对象并设置接收者

命令模式Command Pattern

使用命令模式,我们可以将执行某个任务的对象与调用该方法的对象解耦

假设我们有一个在线食品配送平台。用户可以下达、跟踪和取消订单。

typescript
class OrderManager {
  constructor(private orders = [] as any[]) {}

  placeOrder(order, id) {
    this.orders.push(id);
    return `You have successfully ordered ${order} (${id})`;
  }

  trackOrder(id) {
    return `Your order ${id} will arrive in 20 minutes.`;
  }

  cancelOrder(id) {
    this.orders = this.orders.filter((order) => order.id !== id);
    return `You have canceled your order ${id}`;
  }
}

OrderManager类上,我们可以访问placeOrdertrackOrdercancelOrder方法。直接使用这些方法将是完全有效的 JavaScript

typescript
const manager = new OrderManager();

manager.placeOrder("Pad Thai", "1234");
manager.trackOrder("1234");
manager.cancelOrder("1234");

但是,直接在manager实例上调用方法也有缺点。我们可能会决定稍后重命名某些方法,或者方法的功能发生变化。

假设我们现在不再将其称为placeOrder ,而是将其重命名为addOrder !这意味着我们必须确保不在代码库中的任何位置调用placeOrder方法,这在大型应用程序中可能非常棘手。相反,我们希望将方法与manager对象解耦,并为每个命令创建单独的命令函数!

让我们重构OrderManager类:它将只有一个方法: execute ,而不是placeOrdercancelOrdertrackOrder方法。此方法将执行它给出的任何命令。

typescript
class OrderManager {
  constructor(private orders = [] as any[]) {}

  execute(command, ...args) {
    return command.execute(this.orders, ...args);
  }
}

然后为订单管理器创建三个Command

  • PlaceOrderCommand
  • CancelOrderCommand
  • TrackOrderCommand
typescript
class Command {
  constructor(private execute) {
    this.execute = execute;
  }
}

function PlaceOrderCommand(order, id) {
  return new Command((orders) => {
    orders.push(id);
    return `You have successfully ordered ${order} (${id})`;
  });
}

function CancelOrderCommand(id) {
  return new Command((orders) => {
    orders = orders.filter((order) => order.id !== id);
    return `You have canceled your order ${id}`;
  });
}

function TrackOrderCommand(id) {
  return new Command(() => `Your order ${id} will arrive in 20 minutes.`);
}

完美!这些方法不再直接耦合到OrderManager实例,而是现在是独立的、解耦的函数,我们可以通过OrderManager上可用的execute方法调用它们。

完整代码:

typescript
interface Order {
  order: any;
  id: number;
}

class OrderManager {
  constructor(private orders = [] as Order[]) {}

  execute(command, ...args) {
    return command.execute(this.orders, ...args);
  }
}

class Command {
  constructor(private execute: ([]: Order[]) => any) {}
}

function PlaceOrderCommand(order, id: number) {
  return new Command((orders) => {
    orders.push({ order, id });
    return `You have successfully ordered ${order} (${id})`;
  });
}

function CancelOrderCommand(id: number) {
  return new Command((orders) => {
    orders = orders.filter((order) => order.id !== id);
    return `You have canceled your order ${id}`;
  });
}

function TrackOrderCommand(id: number) {
  return new Command(() => `Your order ${id} will arrive in 20 minutes.`);
}

const manager = new OrderManager();

console.log(manager.execute(PlaceOrderCommand("Pad Thai", 1234)));
console.log(manager.execute(TrackOrderCommand(1234)));
console.log(manager.execute(CancelOrderCommand(1234)));

总结:

优点:

  1. 解耦请求者和执行者:在命令模式中,请求者不直接与接收者交互,而是通过命令对象进行交互,从而消除了请求者和执行者之间的耦合。
  2. 满足“开-闭”原则:如果增加新的具体命令和该命令的接收者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有的具体命令和接收者,新增加的调用者就可以使用自己已有的具体命令。
  3. 可记录日志:由于请求者被封装到了具体命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此使用命令模式可以记录日志。
  4. 可对请求进行排队:使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按照一定的顺序执行这些命令。

缺点:

  1. 命令类膨胀:可能会导致系统中命令类的膨胀,因为每个具体命令都需要一个命令类。
  2. 增加系统复杂度:可能需要创建额外的命令对象,从而增加系统的复杂度。
  3. 降低系统性能:由于需要额外的对象创建和管理,可能会降低系统的性能。