javascript —— 设计模式+真实应用(持续更新)

198 阅读5分钟

前端常用的设计模式以及实际当中的应用

通过本文你可以学到:

  • 策略模式
  • 发布订阅模式
  • 责任链模式

1.策略模式

概念

一个策略模式的程序至少由两个部分组成。第一个部分是一组策略,策略类分装了具体的算法,并负责具体的计算过程。第二部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。

使用场景

场景1:年终结算,按照员工等级来发工资,等级A发4倍,等级B发3倍,等级C发2倍。

场景2:权限校验。比如想进行某个操作,你必须满足XXX条件,这些条件就是策略。 抽象理解:1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。

代码实现

场景1的业务比较明显,我们就用场景2来举例。

需求:我想修改某个用户的权限,条件需满足:role:"admin"level:>3isLogin:true

  • 第一步:定义策略组
    const strategies = {
        checkRole(role) {
            if (role != "admin") {
                    console.warn("当前角色不允许做此操作")
                    return false
            }
            return true;
        },
        checkLevel(level) {
            if (level <= 3) {
                    console.warn("等级不足");
                    return false;
            }
            return true;
        },
        isLogin(bool) {
            if (!bool) {
                    console.warn("当前用户未登录到系统");
                    return false;
            }
            return true;
        }
    
    }
    
  • 第二步:定义环境类(也就是上文提到的context,用来接收用户请求)
    const vaildator = function () {
            this.cache = [];
            
            this.add = function (strategy, value) {
                    this.cache.push(function () {
                            return strategies[strategy](value);
                    })
            }
    
            this.vaildate = function () {
                    for (let i = 0; i < this.cache.length; i++) {
                            const res = this.cache[i]();
                            if (!res) {
                                    console.error("验证错误", this.cache[i]);
                                    return;
                            }
    
                    }
                    return true
            }
    
    
    }
    
  • 第三步:实际使用
    // 操作1
    var user1_result = (function () {
            const instance = new vaildator();
            instance.add("checkRole", "admin");//添加一个策略
            const result = instance.vaildate();
            return result;
    
    })();
    console.log(user1_result);
    

优缺点

优点:算法可以自由切换、避免使用多重条件判断、拓展性良好

缺点:策略类会增多、所有策略接口都需要对外暴露

2.发布订阅模式

概念

在发布订阅模式中,发布者,并不会直接通知订阅者,换句话说,发布者和订阅者,彼此互不相识,而是通过第三者来通知。

使用场景

场景1:模拟一个场景,火箭发射成功之后要通知各部门。

场景2:用户下单成功之后,要通知金钱模块、积分模块。

代码实现

场景2的业务在我们平时的开发中比较常用,我们就用场景2来举例。

需求:用户下单成功之后,要通知金钱模块、积分模块执行相应的业务操作

  • 第一步:定义第三者(因为发布者不会通知订阅者,所以我们需要定义第三者)

    function EventEmit() {
            this.handlers = {};
            this.on = function (event, cb) {
                    if (event in this.handlers) {
                            this.handlers[event].push(cb);
                    } else {
                            this.handlers[event] = [cb];
                    }
            }//实际上就是给this.handlers里面添加属性
            this.emit = function (event, ...args) {
    
                    if (!(event in this.handlers)) return;
                    this.handlers[event].forEach(handle => {
                            handle(...args);
                    })
            }//实际上就是遍历这个事件下面的所有方法
    }
    
  • 第二步:订阅

    // 创建一个第三者实例
    var eventEmit = new EventEmit();
    
    // 积分模块订阅
    function power() {
            var self = this;
            this.name = "积分模块";
            eventEmit.on("success", () => {
                    console.log(self.name + "收到!!!");
                    // TODO 这里做你的业务操作
            })
    }
    new power();
    
    
    // 金钱模块订阅
    function monitoring() {
            var self = this;
            this.name = "金钱模块";
            eventEmit.on("success", () => {
                    console.log(self.name + "收到!!!");
                    // TODO 这里做你的业务操作
            })
    
    }
    new monitoring()
    

    这里说一下为什么要使用new?

    因为默认的this指向是window,我当前函数中用到了this,所以要利用闭包,保存this的指向,用call、bind、apply也可以实现。关于this的问题,可以参考我另外一篇文章

  • 第三步:发布

    eventEmit.emit("success")
    

看一下浏览器输出

image.png

什么时候用发布订阅模式

  • 各模块相互独立
  • 存在一对多的关系
  • 不同人员开发

3.责任链模式

概念

顾名思义,责任链模式就是为请求创建了一个接收者对象的链。意图就是:避免发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象链接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

使用场景

场景1:根据用户的定金来返优惠券,500返100优惠券、200返50优惠券、没定金不返优惠券

抽象理解:1、有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。 2、在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。 3、可动态指定一组对象处理请求。

代码实现

  • 第一步:分别定义(500、200、无定金)的功能
    function order500(orderPrice, stock) {
            if (orderPrice == 500) {
                    console.log("恭喜你得到了100元优惠券");
            } else {
                    return "nextSuccessor";
            }
    }
    function order200(orderPrice, stock) {
            if (orderPrice == 200) {
                    console.log("恭喜你得到了50元优惠券");
            } else {
                    return "nextSuccessor";
            }
    }
    
    function orderNormal(orderPrice, stock) {
            console.log(orderPrice)
            if (orderPrice != (500 || 200) && stock > 0) {
                    console.log("普通购买,无优惠券");
            } else {
                    console.log("库存不足,无法购买");
            }
    }
    
  • 第二步:原型定义after方法
    Function.prototype.after = function (fn) {
        var self = this;
        return function () {
            const ret = self.call(this, ...arguments);
            if (ret == "nextSuccessor") {
                    return fn.call(this, ...arguments);
            }
            return ret;
    
        }
    }
    
  • 第三步:定义责任链
    var result = order500.after(order200).after(orderNormal);
    
  • 第四步:实际应用
    result(500);//恭喜你得到了100元优惠券
    

优缺点

优点:  

1、降低耦合度。它将请求的发送者和接收者解耦。
2、简化了对象。使得对象不需要知道链的结构。
3、增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
4、增加新的请求处理类很方便。

缺点:  

1、不能保证请求一定被接收。
2、系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
3、可能不容易观察运行时的特征,有碍于除错。