前端常用的设计模式以及实际当中的应用
通过本文你可以学到:
- 策略模式
- 发布订阅模式
- 责任链模式
1.策略模式
概念
一个策略模式的程序至少由两个部分组成。第一个部分是一组策略,策略类分装了具体的算法,并负责具体的计算过程。第二部分是环境类Context,Context接受客户的请求,随后把请求委托给某一个策略类。
使用场景
场景1:年终结算,按照员工等级来发工资,等级A发4倍,等级B发3倍,等级C发2倍。
场景2:权限校验。比如想进行某个操作,你必须满足XXX条件,这些条件就是策略。 抽象理解:1、如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中
选择一种行为。 2、一个系统需要动态地在几种算法中选择一种。 3、如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
代码实现
场景1的业务比较明显,我们就用场景2来举例。
需求:我想修改某个用户的权限,条件需满足:role:"admin"、 level:>3、 isLogin: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")
看一下浏览器输出
什么时候用发布订阅模式
- 各模块相互独立
- 存在一对多的关系
- 不同人员开发
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、可能不容易观察运行时的特征,有碍于除错。