Libertus Chen-U
  1. 1 Libertus Chen-U
  2. 2 Life Will Change Lyn
  3. 3 The Night We Stood Lyn
  4. 4 かかってこいよ NakamuraEmi
  5. 5 Quiet Storm Lyn
  6. 6 Warcry mpi
  7. 7 Hypocrite Nush
  8. 8 Last Surprise Lyn
  9. 9 Time Bomb Veela
  10. 10 Flower Of Life 发热巫女
  11. 11 One Last You Jen Bird
2017-04-03 18:27:10

设计模式——策略模式

俗话说条条大路通罗马,在程序设计中不少问题也存在着多种解决方案。不同的解决方案虽然都是用来解决同一个问题,但由于场景不同最适合的方案也不一样,于是很自然的想到将解决方案分别封装起来在合适的场景替换使用。这就是策略模式。


算法封装实例

假设我们现在需要为公司提供一段代码来计算不同员工的年终奖,影响因素有员工的工资基数和年底绩效。绩效有S,A,B三个等级,分别对应4,3,2倍工资。

初始实现

这个逻辑非常简单,凭直觉我们很容易就能写出下面这个计算函数。

let calculate = function(level, salary) {
  if (level === 'S') {
    return salary * 4;
  }
  if (level === 'A') {
    return salary * 3;
  }
  if (level === 'B') {
    return salary * 2;
  }
}

虽然功能我们是实现了,但是存在明显缺点。

1.函数内逻辑耦合过紧,如果以后需要增加新的绩效等级,就必须更改函数内部实现。

2.复用性差。如果相同的算法需要用到其他模块中,就必须复制整个函数实现。

策略模式重构

将不变的部分和变化的部分隔离开来是设计模式的核心。我们使用策略模式,来将上述算法的调用和算法内部逻辑隔离开来。

let strategies = {
  S(salary) {
    return salary * 4;
  },
  A(salary) {
    return salary * 3;
  },
  B(salary) {
    return salary * 2;
  }
};
let calculate = function(level, salary) {
  return strategies[level](salary);
};

可以看到重构之后代码更加清晰,可拓展性和复用性也更强了。我们将业务逻辑和算法分开封装,当业务执行到需要不同算法实现时,再将职责委托给算法对象来识别并计算。这正是对象的多态性体现。

业务封装实例

策略模式的本意虽然是用来封装算法,但实际业务中我们可以将这个概念泛化为解决某一问题的逻辑。只要目标一致并且可以在适当的时机替换使用,就可以使用策略模式来封装。例如我们现在要实现一个表单验证功能。需求如下:

  1. 用户名不能为空。
  2. 密码长度不少于6位。
  3. 手机号码必须符合格式。

初始实现

按照常规逻辑,很容易写出如下代码:

<html>
 <body>
   <form action="..." id="registerForm" method="post">
     用户名:<input type="text" name="userName"/>
     密码:<input type="text" name="password"/>
     手机号码:<input type="text" name="phoneNumber"/>
     <input type="sumbit" value="Submit"/>
   </form>
   <script>
       let registerForm = document.getElementById('registerForm');
     registerForm.onsubmit = function() {
       if (registerForm.userName.value === '') {
         alert('用户名不能为空');
         return false;
       }
       if (registerForm.password.value.length < 6) {
         alert('密码长度不能少于6位');
         return false;
       }
       if (!/(^1[3|5|8[0-9]{9}]$)/.test(registerForm.phoneNumber.value)) {
         alert('手机号码格式不正确');
         return false;
       }
     }
   </script>
  </body>
</html>

虽然功能是实现了,但缺点和之前的算法一样。我们试着用策略模式来重构。

let strategies = {
  //用户名验证
  isEmpty(value, errorMsg) {
    if (value === '') {
      return errorMsg;
    }
  },
  // 密码长度验证
  minLength(value, length, errorMsg) {
    if (value.length < length) {
      return errorMsg;
    }
  },
  // 格式验证
  isMobile(value, errorMsg) {
    if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
      return errorMsg;
    }
  }
};

首先我们将验证策略封装为策略对象。然后再编写一个验证逻辑函数去实现功能。

let validata = function() {
  let validator = new Validator(); // 创建验证器
  validator.add(registerForm.userNmae, 'isEmpty', '用户名不能为空');
  validator.add(registerForm.passWord, 'minLength: 6', '密码长度不能少于6位');
  validator.add(registerForm.userNmae, 'isMobile', '手机号码格式不正确');
  let errorMsg = validator.start(); // 启动校验
  return errorMsg;
}

可以看到,我们创建了一个可以启动验证并返回错误消息的函数。在内部,我们创建了一个验证器对象,并将验证策略添加到对象中,然后通过start方法将验证逻辑委托给验证器。下面只要完善验证器的构造函数即可。

let Validator = function() {
  this.cache = []; //保存验证规则用于启动时遍历
};
// 添加原型方法
Validator.prototype.add = function(dom, rule, errorMsg) {
  let ary = rule.split(':'); //分离带参策略
  this.cache.push(function() {
    let strategy = ary.shift();
    ary.unshift(dom.value) // 表单值添加到参数列表
    ary.push(errorMsg); //错误信息添加到参数列表
    return strategies[strategy].apply(dom, ary) // 委托验证
  });
  Validator.prototype.start = function() {
    for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
      let msg = validatorFunc();
      if (msg) {
        return msg;
      }
    }
  }
}

重构之后,这一套校验规则就可以复用了。而且当我们需要修改规则时可以非常方便的到策略对象中修改。

总结

策略模式也是一种常用并且有效的设计模式。虽然会在程序中增加一些策略对象,但长远来看增强了程序的拓展性和复用性。当系统越来越复杂时,将会避免许多重复性工作。

-- EOF --

添加在分类「 前端开发 」下,并被添加 「设计模式」 标签。

文章目录

 - [算法封装实例](#算法封装实例)
   - [初始实现](#初始实现)
   - [策略模式重构](#策略模式重构)
 - [业务封装实例](#业务封装实例)
   - [初始实现](#初始实现)
 - [总结](#总结)
回到首页