命令模式
命令模式通常包含以下几个角色:
- 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类上,我们可以访问placeOrder 、 trackOrder和cancelOrder方法。直接使用这些方法将是完全有效的 JavaScript!
typescript
const manager = new OrderManager();
manager.placeOrder("Pad Thai", "1234");
manager.trackOrder("1234");
manager.cancelOrder("1234");但是,直接在manager实例上调用方法也有缺点。我们可能会决定稍后重命名某些方法,或者方法的功能发生变化。
假设我们现在不再将其称为placeOrder ,而是将其重命名为addOrder !这意味着我们必须确保不在代码库中的任何位置调用placeOrder方法,这在大型应用程序中可能非常棘手。相反,我们希望将方法与manager对象解耦,并为每个命令创建单独的命令函数!
让我们重构OrderManager类:它将只有一个方法: execute ,而不是placeOrder 、 cancelOrder和trackOrder方法。此方法将执行它给出的任何命令。
typescript
class OrderManager {
constructor(private orders = [] as any[]) {}
execute(command, ...args) {
return command.execute(this.orders, ...args);
}
}然后为订单管理器创建三个Command:
PlaceOrderCommandCancelOrderCommandTrackOrderCommand
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)));总结:
优点:
- 解耦请求者和执行者:在命令模式中,请求者不直接与接收者交互,而是通过命令对象进行交互,从而消除了请求者和执行者之间的耦合。
- 满足“开-闭”原则:如果增加新的具体命令和该命令的接收者,不必修改调用者的代码,调用者就可以使用新的命令对象;反之,如果增加新的调用者,不必修改现有的具体命令和接收者,新增加的调用者就可以使用自己已有的具体命令。
- 可记录日志:由于请求者被封装到了具体命令中,那么就可以将具体命令保存到持久化的媒介中,在需要的时候,重新执行这个具体命令。因此使用命令模式可以记录日志。
- 可对请求进行排队:使用命令模式可以对请求者的“请求”进行排队。每个请求都各自对应一个具体命令,因此可以按照一定的顺序执行这些命令。
缺点:
- 命令类膨胀:可能会导致系统中命令类的膨胀,因为每个具体命令都需要一个命令类。
- 增加系统复杂度:可能需要创建额外的命令对象,从而增加系统的复杂度。
- 降低系统性能:由于需要额外的对象创建和管理,可能会降低系统的性能。