定义
命令模式(Command Pattern)是一种行为型设计模式,是一个高内聚的模式,它将一个请求封装成一个对象,从而使你可以用不同的请求对客户端参数化。命令模式可以让请求者和接收者解耦,并支持撤销操作。
命令模式的一个核心就是 “解耦” 。
那是谁发生了耦合呢?
答:请求发起者和请求接收者的耦合。典型的例子就是餐厅点餐,由顾客点好菜单,这个菜单就是”命令”,然后 “接收者” 厨师开始制作;倘若没有解耦,那就是由顾客直接跟厨师交代事情。命令模式非常像消息队列,有消息生产者、消费者、以及消息命令。
命令模式是一种数据驱动的设计模式,将请求命令封装成一个命令对象,由调用者将这个命令对象发给执行者。
在命令模式中,通常会有四个角色:
1.命令(Command):封装了一个请求及其相关的操作。
2.具体命令(Concrete Command):实现了命令接口,负责执行具体的请求操作。
3.接收者(Receiver):负责执行请求操作。
4.调用者(Invoker):负责调用命令对象来执行请求操作,并可以支持撤销操作。
命令模式的优点
- 可以将命令对象与调用者解耦,使得调用者不需要知道具体的请求操作是如何实现的。
- 可以将请求对象与接收者解耦,使得接收者不需要知道具体的请求对象是如何创建和执行的。
- 支持撤销操作,因为命令对象通常会存储执行操作所需的状态信息。
- 可以方便地增加新的命令对象,因为每个命令对象都实现了命令接口,它满足“开闭原则”,对扩展比较灵活。
命令模式的缺点
- 可能会导致系统中出现大量的具体命令类。
- 增加了系统的复杂度和开销,因为需要多个对象来实现一个请求操作。
- 可能会影响系统的性能,因为需要多次调用对象的方法。
举个例子
如果想实现一个购物车功能,有添加产品、删除产品、更改产品等功能。就比较适用于命令模式了。
1. 命令四角色 —命令接口:
interface ICommand {
execute(): void;
undo(): void;
}
2. 命令四角色 — 实现命令接口:(ConcreteCommand):
class AddProductCommand implements ICommand {
constructor(private product: Product, private shoppingCart: ShoppingCart){}
execute() {
this.shoppingCart.addProduct(this.product)
}
undo() {
this.shoppingCart.removeProduct(this.product)
}
}
class RemoveProductCommand implements ICommand {
constructor(private product: Product, private shoppingCart: ShoppingCart){}
execute() {
this.shoppingCart.removeProduct(this.product)
}
undo() {
this.shoppingCart.addProduct(this.product)
}
}
class ChangeProductQuantityCommand implements ICommand {
private readonly oldQuantity: number;
constructor(private product: Product, private shoppingCart: ShoppingCart, private newQuantity: number){
this.oldQuantity = this.product.quantity
}
execute() {
this.product.quantity = this.newQuantity
}
undo() {
this.product.quantity = this.oldQuantity
}
}
3. 命令四角色 — 接收者(Receiver):
class Product {
constructor(public readonly id: number, public readonly name: string, public readonly price: number, public quantity: number) {}
get total() {
return this.price * this.quantity
}
}
class ShoppingCart {
private readonly products: Product[] = []
addProduct(product: Product) {
const existingProduct = this.products.find(item => item.id === product.id)
if (existingProduct) {
existingProduct.quantity ++
} else {
this.products.push(product)
}
}
removeProduct(product: Product) {
const index = this.products.findIndex(p => p.id === product.id);
if (index >= 0) {
const finded = this.products[index]
if (finded.quantity === 1) {
this.products.splice(index, 1)
} else {
finded.quantity --
}
}
}
getTotalPrice() {
return this.products.reduce((total, product) => total + product.total , 0)
}
print() {
this.products.forEach(item => {
console.log(`${item.name}: 单价是${item.price},数量是${item.quantity}, 共${item.total}钱`)
})
}
}
3. 命令四角色 — 调用者(Invoker):
class Invoker {
private readonly commands:ICommand[] = []
clear() {
this.commands.length = 0
}
addCommand(command: ICommand) {
this.commands.push(command)
return this
}
executeCommands() {
this.commands.forEach(command => command.execute());
}
undoCommands() {
this.commands.slice().reverse().forEach(command => command.undo());
}
}
最后调用代码如下,
const shoppingCart = new ShoppingCart()
const product1 = new Product(1, 'apple', 30, 2)
const product2 = new Product(2, 'banana', 10, 3)
const product3 = new Product(3, 'pie', 50, 1)
// 添加商品
const addProductCommand1 = new AddProductCommand(product1, shoppingCart)
const addProductCommand2 = new AddProductCommand(product2, shoppingCart)
const addProductCommand3 = new AddProductCommand(product3, shoppingCart)
// 删除商品
const removeProductCommand = new RemoveProductCommand(product2, shoppingCart)
// 修改数量
const changeProductQuantityCommand = new ChangeProductQuantityCommand(product3, shoppingCart, 20)
const invoke = new Invoker()
invoke.addCommand(addProductCommand1).addCommand(addProductCommand2).addCommand(addProductCommand3)
invoke.executeCommands()
shoppingCart.print()
invoke.clear()
invoke.addCommand(removeProductCommand)
invoke.executeCommands()
shoppingCart.print()
invoke.clear()
invoke.addCommand(changeProductQuantityCommand)
invoke.executeCommands()
shoppingCart.print()
invoke.clear()
invoke.addCommand(changeProductQuantityCommand)
invoke.undoCommands()
shoppingCart.print()
总结
上面那个购物车例子,定义一个公用的命令接口类型,每个命令都有一个execute和undo方法,然后基于这个类型去实现不同的命令。 命令的接收者就是ShoppingCart类,接收者负责命令的具体实现逻辑。 如何去调用命令呢,就是Invoker类,起到一个调用命令的作用。