好家伙,
在软件系统中,“行为请求者”与“行为实现者”通常呈现一种“紧耦合”。
但在某些场合,比如要对行为进行“记录、撤销/重做、事务”等处理,这种无法抵御变化的紧耦合是不合适的。
在这种情况下,如何将“行为请求者”与“行为实现者”解耦?将一组行为抽象为对象,实现二者之间的松耦合。这就是命令模式(Command Pattern)。
Command(定义接口): 定义命令的接口,声明执行的方法。
ConcreteCommand(实现接口):命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver(接收者):接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker(调用者):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。
这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client(具体命令):创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,
把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行
想象一个实例
餐厅点餐案例
现在我走进一家餐厅,向一名服务员点餐,点了一份牛肉汉堡,
服务员将这份”牛肉汉堡"订单交给厨师,厨师制作这份牛肉牛肉汉堡
来个代码实现
// 厨师类 class Chef { makeBurger() { console.log("This is your burger."); } } // 服务员类 class Waitress { constructor(chef) { this.chef = chef; } takeOrder(order) { // 服务员接收订单并处理 console.log("Waitress is taking the order for a " + order); this.chef.makeBurger(); // 直接通知厨师制作汉堡 } } // 客户端代码 (() => { const chef = new Chef(); const waitress = new Waitress(chef); // 客户点餐 服务员通知 waitress.takeOrder("burger"); })();复制
那么如果我使用命令模式去实现呢?
// 命令接口(抽象概念,使用ES6的类来表示) class Command { execute() { // 抽象方法execute,将在具体命令中实现 throw new Error('You should implement the execute method'); } } // 具体命令类 class OrderBurgerCommand extends Command { constructor(receiver) { super(); this.receiver = receiver; } execute() { this.receiver.makeBurger(); } } // 接收者类 class Chef { makeBurger() { console.log("Chef is making a burger."); } } // 请求者类 class Waitress { constructor() { this.commands = []; } takeOrder(command) { if (!(command instanceof Command)) { throw new Error('You can only take order of Command instances'); } this.commands.push(command); } notify() { this.commands.forEach(command => command.execute()); } } // 客户类 class Customer { constructor() { this.waitress = new Waitress(); this.chef = new Chef(); this.burgerCommand = new OrderBurgerCommand(this.chef); } orderBurger() { this.waitress.takeOrder(this.burgerCommand); } serveOrder() { this.waitress.notify(); } } // 客户端代码 (() => { const customer = new Customer(); customer.orderBurger(); // 客户点餐 customer.serveOrder(); // 服务员通知厨师制作汉堡 })();复制
同样的,如果我们新下单一条“薯条”
使用命令模式
// 命令模式实现 // 命令接口 class Command { execute() { // 抽象方法,需要在子类中实现 } } // 汉堡命令 class BurgerCommand extends Command { constructor(chef) { super(); this.chef = chef; } execute() { this.chef.makeBurger(); } } // 薯条命令 class FrenchFriesCommand extends Command { constructor(chef) { super(); this.chef = chef; } execute() { this.chef.makeFrenchFries(); } } // 厨师类 class Chef { makeBurger() { console.log('制作汉堡'); } makeFrenchFries() { console.log('制作薯条'); } } // 服务员类 class Waiter { constructor() { this.commands = []; } order(command) { this.commands.push(command); console.log('订单已接收'); } serve() { this.commands.forEach(command => command.execute()); } } // 客户类 class Customer { constructor(waiter) { this.waiter = waiter; } orderBurger() { const chef = new Chef(); const burgerCommand = new BurgerCommand(chef); this.waiter.order(burgerCommand); } orderFrenchFries() { const chef = new Chef(); const friesCommand = new FrenchFriesCommand(chef); this.waiter.order(friesCommand); } } // 客户端代码 (() => { const waiter = new Waiter(); const customer = new Customer(waiter); // 客户点一份汉堡 customer.orderBurger(); // 客户再点一份薯条 customer.orderFrenchFries(); // 服务员开始服务 waiter.serve(); })();复制
再结合以上代码来看,命令模式的优势有
1.真正实现了解耦:客户不需要知道汉堡制作的细节,服务员也不需要知道汉堡制作的细节,客户仅仅是下单,服务员仅仅是通知
2.易于扩展:如果需要添加新的操作(如包装、加热),可以创建新的命令类,而无需修改现有的类结构。
通过汉堡和薯条的例子,我们可以看到命令模式如何使得代码更加灵活、可维护,并且更容易进行扩展。