JS设计模式-策略模式
mmtdxslb
8年前
<h2>前情提要</h2> <p>面向过程:将项目分解成很多步骤,为每个步骤编写代码。可维护性差。</p> <p>面向对象:将项目分解成一个一个方法,然后根据每个步骤的需要调用方法。可维护性好。</p> <p>面向对象的SOLID原则</p> <ol> <li> <p>单一职责:一个类有且只有一个职责</p> </li> <li> <p>开放封闭:软件实体(类,模块,函数等)应该对扩展开放,对修改关闭</p> </li> <li> <p>里氏替换:子类型必须能够替换它们的基类</p> </li> <li> <p>接口分离:接口只应该包括必要的方法而不是所有的</p> </li> <li> <p>依赖倒置:高层次的模块不应该依赖于低层次的模块,而是都应该依赖于抽象</p> </li> </ol> <h2>设计模式是啥?</h2> <p>设计模式是一套经验,是对于某一类特定问题的简洁而优雅的解决方案</p> <ol> <li> <p>代码会具有更好的可维护性</p> </li> <li> <p>学习设计模式,帮助我们更好的理解与运用SOLID</p> </li> </ol> <h2>设计模式-策略模式</h2> <p>应用场景:当需要根据不同情景从多种算法中选择一种执行时</p> <p>重点思想:将变与不变分离</p> <p>具体实现:将 不变的 算法封装在 策略类 中, 策略类 只负责算法,传入参数传出参数; 变化的 是客户端的请求,放在 情景类 中, 情景类 负责接收客户端的请求并委托给 策略类</p> <h3>举例1:会员折扣价格计算</h3> <p>需求:对 A 类型会员提供 20% 的促销折扣,对 B 类型会员提供 10% 的促销折扣,对 C 类型会员没有折扣</p> <p>首先我们定义会员折扣</p> <pre> <code class="language-javascript">// 定义不同会员的折扣 const discountA = 0.8, discountB = 0.9, discountC = 1;</code></pre> <p>最初的实现↓</p> <pre> <code class="language-javascript">// 计算折扣价格函数 function calculatePrice(type, totalPrice) { if(type === 'A') { return totalPrice*discountA; } if(type === 'B') { return totalPrice*discountB; } if(type === 'C') { return totalPrice*discountC; } } // 调用函数 console.log(calculatePrice('A',100));</code></pre> <p>分析:算法是不变的,变的是会员类型和总价</p> <p>运用策略模式↓</p> <pre> <code class="language-javascript">// 定义策略对象 const strategies = { A: function(totalPrice) { return totalPrice*discountA; }, B: function(totalPrice) { return totalPrice*discountB; }, C: function(totalPrice) { return totalPrice*discountC; }, } // 定义情景类 function calculatePrice(type, totalPrice) { return strategies[type](totalPrice); } // 调用函数计算折扣价 console.log(calculatePrice('A',100));</code></pre> <p>策略对象还可以进一步抽象。</p> <pre> <code class="language-javascript">// 定义策略对象 const strategies = { A: discountA, B: discountB, C: discountC, } // 定义情景类 function calculatePrice(type, totalPrice) { return strategies[type]*(totalPrice); } // 调用函数计算折扣价 console.log(calculatePrice('A',100));</code></pre> <h3>举例2:注册页面表单验证</h3> <p>需求: 用户名 不可为空, 密码 不能小于6位, 手机号码 格式正确</p> <pre> <code class="language-javascript">// 伪一组数据 const registerForm = { userName: 'liuxiaocui', password: 'lxc123456', phone: '18628337983', }</code></pre> <p>最初的实现↓</p> <pre> <code class="language-javascript">// 校验函数 function validate(data) { if(data.userName === '') { console.log('用户名不能为空'); return; } if (data.password.length < 6) { console.log('密码长度不能小于6位'); return; } if (!/(^1[3|5|8][0-9]{9}$)/.test(data.phone)) { console.log('手机号码格式不正确'); return; } return true; } // 提交表单函数 function onSubmit(data) { if(validate(data)) { console.log('通过校验'); // 别的操作blabla } } // 调用提交表单函数 onSubmit(registerForm);</code></pre> <p>分析:校验算法是不变的,变的是数据和校验要求</p> <p>运用策略模式↓</p> <p>首先定义策略对象,注意每个函数的传参。所有参数应该外部传入。</p> <pre> <code class="language-javascript">// 定义策略对象 const strategies = { // 校验为空 isNonEmpty: function (value, errorMsg) { if (value === '') { return errorMsg; } }, // 校验最小长度 minLength: function (value, length, errorMsg) { if (value.length < length) { return errorMsg; } }, // 校验手机号码格式 isMobile: function (value, errorMsg) { if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) { return errorMsg; } } }</code></pre> <p>回顾下需求。我们希望能方便得对数据进行校验,也很可能一个数据需要多条校验规则。</p> <p>先来看下校验函数是怎么写的。</p> <pre> <code class="language-javascript">// 校验函数 function validate(data) { const validator = new Validator(); validator.add(data.userName, [{ strategy: 'isNonEmpty', errorMsg: '用户名不能为空' }, { strategy: 'minLength:10', errorMsg: '用户名长度不能小于10位' }]); validator.add(data.password, [{ strategy: 'minLength:6', errorMsg: '密码长度不能小于6位' }]); validator.add(data.phone, [{ strategy: 'isMobile', errorMsg: '手机号码格式不正确' }]); return validator.start(); }</code></pre> <pre> <code class="language-javascript">// 提交表单函数 function onSubmit(data) { const errorMsg = validate(data); if(errorMsg) { console.log(errorMsg); } else { console.log('通过校验'); // 别的操作blabla } } // 调用函数 onSubmit(registerForm);</code></pre> <p>下面是情景类。</p> <pre> <code class="language-javascript">// 定义情景类 var Validator = function() { // 存下需要进行的校验 this.cache = []; } // 增加需要进行的校验 Validator.prototype.add = function(data, rules) { rules.forEach( item => { // strategyAry的第一个元素为strategy名字,第二个值(如果有)为限制值 const strategyAry = item.strategy.split(':'); const errorMsg = item.errorMsg; this.cache.push(function() { const strategy = strategyAry.shift(); // unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度。 // 把要校验的数据放在数组第一位 strategyAry.unshift(data); // 把错误信息放在数组最后一位 strategyAry.push(errorMsg); // return strategies[strategy](...strategyAry); return strategies[strategy].apply(null,strategyAry); }) }) }</code></pre> <pre> <code class="language-javascript">// 开始校验 Validator.prototype.start = function() { // 挨个运行this.cache中的校验函数 for(let validatorFunc of this.cache) { const errorMsg = validatorFunc(); if(errorMsg) { return errorMsg; } } }</code></pre> <h3>小结</h3> <p>策略模式优点:将不变的算法单独封装在 策略类 中,通过 情景类 来接收请求并委托给 策略类 ,代码结构清晰,使用方便</p> <p>策略模式缺点:使用之前需要知道所有的策略类函数</p> <p> </p> <p>来自:https://segmentfault.com/a/1190000008773507</p> <p> </p>