JS设计模式-策略模式

mmtdxslb 7年前
   <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>