设计模式 - 命令模式 - JavaScript

命令模式定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

专注前端与算法的系列干货分享。
引用请声明:xxoo521.com | 「公众号:心谭博客」

什么是“命令模式”?

命令模式(别名:动作模式、事务模式)定义:将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

简单来说,它的核心思想是:不直接调用类的内部方法,而是通过给“指令函数”传递参数,由“指令函数”来调用类的内部方法。

在这过程中,分别有 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(` At ${Date.now()}, call ${cmd}`); // 真实场景中,可以向数据库写入日志,或者微服务上报日志
    return Mall[cmd](...args);
}

最后,下面代码展示了外界的“调用者”如何调用命令:

// 调用者
console.log(execCmd("request", 10001));
console.log("10个mbp的总价是", execCmd("buy", 10002, 10));

更多思考

在写这篇文章的时候,发现“命令模式”的思路,可以很好的组织不同版本的 api 调用。只需要在“传递者”函数中进行版本识别,然后传递到对应版本的类中即可。

这对于外界调用者来说,是无感的。即便想调用老版本的函数 api,也可以通过给“传递者”函数指定代表版本的参数来实现。

参考

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×