共计 5028 个字符,预计需要花费 13 分钟才能阅读完成。
何为策略模式?
- 比方在业务逻辑或程序设计中比方要实现某个性能,有多种计划可供咱们抉择。比方要压缩一个文件,咱们既能够抉择 ZIP 算法,也能够抉择 GZIP 算法。
- 这些算法灵活多样,可随便切换,而这种解决方案就是咱们所要学习的策略模式。
定义或概念
策略模式:定义一系列的算法,将他们一个个封装,并使他们可互相替换。
策略模式的最佳实际
例子 1:奖金计算
- 题目:在很多公司的年终奖都是依照员工的工资基数和年底绩效状况来发放的,例如,绩效为 S 的人年终奖有 4 倍工资,A 的人年终奖有 3 倍,B 的人年终奖有 2 倍。要求咱们写出一个程序来更快的计算员工的年终奖。(编写一个名为 calcBonus 办法来计算每个员工的奖金数额)
-
可能有些人一上来间接就在一个办法中进行很多 if…else 或 switch…case 判断, 而后通过这个办法进行计算。咱们能够来试着写一下:
/** * * @param {*} level 绩效等级 * @param {*} salary 工资基数 * @returns 年终奖金额 */ var calcBonus = function (level, salary) {if (level === "S") {return salary * 4;} else if (level === "A") {return salary * 3;} else if (level === "B") {return salary * 2;} }; calcBonus('A', 20000); // 60000 calcBonus('B', 8000); // 16000
-
我想在咱们每个人初学代码时必定都写出过这样的代码。其实这段代码有不言而喻的毛病:
- calcBonus 函数逻辑太多
- calcBonus 函数不足弹性,比方如果咱们须要减少一个等级 C,那就必须要去批改 calcBonus 函数。这就违反了凋谢 - 关闭准则。
- 复用性差。如果后续还要重用这个程序去计算奖金,咱们只有去 C,V。
-
此时,可能会想对 calcBonus 函数进行封装,如咱们应用组合函数的模式,如下:
var totalS = function (salary) {return salary * 4;}; var totalA = function (salary) {return salary * 3;}; var totalB = function (salary) {return salary * 2;}; var calcBonus = function (level, salary) {if (level === "S") {return totalS(salary); } else if (level === "A") {return totalA(salary); } else if (level === "B") {return totalB(salary); } }; calcBonus('A', 20000); // 60000 calcBonus('B', 8000); // 16000
- 这样,咱们将程序进行了进一步改善,但改善微不足道,仍旧没有解决最重要的问题,calcBonus 函数还是有可能会很宏大,并且也没有弹性。
- 那咱们再将它进行一次革新,应用策略模式:
将其定义为一系列的算法,将他们每一个封装起来,将不变的局部和变动的局部隔开。
- 在这段程序中,
算法的应用形式是不变的,都是依据某个算法获取最初的奖金金额。而在每个算法的外部实现却是不同的,每一个等级对应着不同的计算规定
。 - 而
在策略模式程序中:起码由两局部组成,一部分是一组策略类,在策略类中封装了具体的算法,并负责具体的计算过程。一部分是环境类 context,承受用户的申请,并将申请委托给某一个策略类。
-
如下:
var strategies = {S: function (salary) {return salary * 4;}, A: function (salary) {return salary * 3;}, B: function (salary) {return salary * 2;}, }; var calcBonus = function (level, salary) {return strategies[level](salary); } calcBonus('A', 20000); // 60000 calcBonus('B', 8000); // 16000
- 其实,
策略模式的实现并不简单,要害是如何从策略模式的实现背地,找到封装变动,委托和多态性这些思维的价值
。
例子 2:表单验证
- 题目:在 Web 开发中,表单校验是一个常见的话题,要求应用策略模式来实现表单验证。
-
比方:
- 用户名不能为空
- 明码长度不能少于 6 位
- 手机号码必须合乎正确格局
-
让咱们来实现一下吧:
function submit() {let { username, password, tel} = infoForm; if (username === "") {Toast("用户名不能为空"); return false; } if (password.length < 6) {Toast("明码不能少于 6 位"); return false; } if (!/(^1[3|5|8][0-9]{9}$)/.test(tel)) {Toast("手机号码格局不正确"); return false; } // ..... }
-
这是咱们常见的实现形式,它的毛病跟计算奖金一例相似:
- submit 函数宏大,蕴含了很多 if…else 语句
- submit 函数不足弹性,如果对其新加一些新的校验规定,如果咱们把明码长度从 6 改到 8. 那咱们就必须要改变 submit 函数,否则无奈实现该校验。这也是违反凋谢 - 关闭准则。
- 复用差,如果说咱们程序中还有另一个表白须要验证,也是进行相似的校验,那咱们可能会进行 C, V 操作。
-
应用策略模式来进行重构
let infoForm = { username: "我是某某某", password: 'zxcvbnm', tel: 16826384655, }; var strategies = {isEmpty: function (val, msg) {if (!val) return msg; }, minLength: function (val, length, msg) {if (val.length < length) return msg; }, isTel: function (val, msg) {if (!/(^1[3|5|8][0-9]{9}$)/.test(val)) return msg; }, }; var validFn = function () {var validator = new Validator(); let {username, password, tel} = infoForm; validator.add(username, "isEmpty", "用户名不能为空"); validator.add(password, "minLength:6", "明码不能少于 6 位"); validator.add(tel, "isTel", "手机号码格局不正确"); var msg = validator.start(); return msg; }; class Validator {constructor() {this.cache = []; } add(attr, rule, msg) {var ruleArr = rule.split(":"); this.cache.push(function () {var strategy = ruleArr.shift(); ruleArr.unshift(attr); ruleArr.push(msg); return strategies[strategy].apply(attr, ruleArr); }); } start() {for (let i = 0; i < this.cache.length; i++) {var msg = this.cache[i](); if (msg) return msg; } } } function submit() {let msg = validFn(); if (msg) {Toast(msg); return false; } console.log('verify success'); // ..... } submit();
- 应用策略模式重构后,咱们后续仅需配置的形式来实现。
- 扩大题目:那如果想给用户名还想再增加一个规定,那如何实现呢?
-
增加规定形式如下:
validator.add(username, [ { strategy: "isEmpty", msg: "用户名不能为空" }, { strategy: 'minLength:6', msg: '明码不能少于 6 位' } ]);
-
实现:
let infoForm = { username: "阿斯顿产生的", password: "ss1sdf", tel: 15829485647, }; var strategies = {isEmpty: function (val, msg) {if (!val) return msg; }, minLength: function (val, length, msg) {if (val.length < length) return msg; }, isTel: function (val, msg) {if (!/(^1[3|5|8][0-9]{9}$)/.test(val)) return msg; }, }; var validFn = function () {var validator = new Validator(); let {username, password, tel} = infoForm; validator.add(username, [ { strategy: "isEmpty", msg: "用户名不能为空", }, { strategy: "minLength:6", msg: "明码不能少于 6 位", }, ]); validator.add(password, [ { strategy: "minLength:6", msg: "明码不能少于 6 位", }, ]); validator.add(tel, [ { strategy: "isTel", msg: "手机号码格局不正确", }, ]); var msg = validator.start(); return msg; }; class Validator {constructor() {this.cache = []; } add(attr, rules) {for (let i = 0; i < rules.length; i++) {var rule = rules[i]; var ruleArr = rule.strategy.split(":"); var msg = rule.msg; var cacheItem = this.createCacheItem(ruleArr, attr, msg); this.cache.push(cacheItem); } } start() {for (let i = 0; i < this.cache.length; i++) {var msg = this.cache[i](); if (msg) return msg; } } createCacheItem(ruleArr, attr, msg) {return function () {var strategy = ruleArr.shift(); ruleArr.unshift(attr); ruleArr.push(msg); return strategies[strategy].apply(attr, ruleArr); }; } } function submit() {let msg = validFn(); if (msg) {Toast(msg); return false; } console.log("verify success"); // ..... } submit();
策略模式的优缺点
-
长处:
- 利用组合,委托,多态等技术无效防止了多重条件语句
- 提供了对开封 - 关闭准则的完满反对
- 复用性较强,防止许多反复的 C,V 工作
-
毛病:
- 客户端比方理解所有的策略类,并抉择适合的策略类。
策略模式的角色
- Context(环境类):持有一个 Strategy 类的援用,用一个 ConcreteStrategy 对象来配置
- Strategy(环境策略类):定义了所有反对的算法的公共接口,通常是以一个接口或形象来实现。Context 应用这个接口来调用其 ConcreteStrategy 定义的算法。
- ConcreteStrategy(具体策略类):以 Strategy 接口实现某种算法
- 比方以上的例子算法:
策略模式的利用场景
- 想应用对象中各种不同算法变体来在运行时切换算法时
- 领有很多在执行某些行为时有着不同的规定时
Tip: 文章局部内容参考于 曾探
大佬的《JavaScript 设计模式与开发实际》。文章仅做集体学习总结
正文完
发表至: javascript
2023-08-24