这是我参与8月更文挑战的第16天,活动详情查看:8月更文挑战
我想学会设计模式之命令模式
命令模式定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
什么是“命令模式”?
命令模式(别名:动作模式、事务模式)定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
简单来说,它的核心思想是:不直接调用类的内部方法,而是通过给“指令函数”传递参数,由“指令函数”来调用类的内部方法。-
在这过程中,分别有 3 个不同的主体:
- 调用者
- 传递者
- 执行者
应用场景
当想降低调用者与执行者(类的内部方法)之间的耦合度时,可以使用此种设计模式。比如:设计一个命令队列,将命令调用记入日志。
ES6 实现
为了方便演示,这里模拟了购物的场景。封装一个商场类,可以查看已有商品的名称和单价。
// 为了方便演示,mock的假数据
const mockData = {
10001: {
name: "电视",
price: 3888
},
10002: {
name: "MacPro",
price: 17000
}
};
/**
* 商品类(执行者)
*/
class Mall {
static request(id) {
if (!mockData[id]) {
return `商品不存在`;
}
const { name, price } = mockData[id];
return `商品名: ${name} 单价: ${price}`;
}
static buy(id, number) {
if (!mockData[id]) {
return `商品不存在`;
}
if (number < 1) {
return `至少购买1个商品`;
}
return mockData[id].price * number;
}
}
毫无疑问,我们可以直接调用商场类上的方法。但是这样会增加调用者和执行者的耦合度。如果之后的函数名称改变了,那么修改成本自然高。
根据命令模式的思想,封装一个“传递者”函数,专门用来传递指令和参数。如果之后商场类的函数名改变了,只需要在“传递者”函数中做个简单映射即可。
/**
* 传递者
*/
function execCmd(cmd, ...args) {
if (typeof Mall[cmd] !== "function") {
return;
}
console.log(`<LOG> At ${Date.now()}, call ${cmd}`); // 真实场景中,可以向数据库写入日志,或者微服务上报日志
return Mall[cmd](...args);
}
最后,下面代码展示了外界的“调用者”如何调用命令:
// 调用者
console.log(execCmd("request", 10001));
console.log("10个mbp的总价是", execCmd("buy", 10002, 10));
更多思考
在写这篇文章的时候,发现“命令模式”的思路,可以很好的组织不同版本的 api 调用。只需要在“传递者”函数中进行版本识别,然后传递到对应版本的类中即可。
这对于外界调用者来说,是无感的。即便想调用老版本的函数 api,也可以通过给“传递者”函数指定代表版本的参数来实现。