说说你对策略模式的理解

策略模式是什么?

策略模式(Strategy Pattern)指的是定义一系列的算法,把它们一个个封装起来,目的就是将算法的使用与算法的实现分离开来。

我们通常会把算法的含义扩散开来,使策略模式也可以用来封装 一系列的“业务规则”。只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以 用策略模式来封装它们。

将不变的部分和变化的部分隔开是每个设计模式的主题,策略模式也不例外,策略模式的目的就是将算法的使用与算法的实现分离开来

一个基于策略模式的程序至少由两部分组成:

  • 策略类

    策略类封装了具体的算法,并负责具体的计算过程。

  • 环境类

    接受客户的请求,随后 把请求委托给某一个策略类

策略模式如何实现?

举个栗子:比如公司的年终奖是根据员工的工资和绩效来考核的,年终奖规则如下

  • 绩效为A的人,年终奖为工资的4倍
  • 绩效为B的人,年终奖为工资的3倍
  • 绩效为C的人,年终奖为工资的2倍

现在我们使用一般的编码方式会如下这样编写代码:

// A算法
var performanceA = function(salary) {
    return salary * 4;
};
// B算法
var performanceB = function(salary) {
    return salary * 3;
};
// C算法
var performanceC = function(salary) {
    return salary * 2
};
// 计算年终奖
var calculateBouns = function(level,salary) {
    if(level === 'A') {
        return performanceA(salary);
    }
    if(level === 'B') {
        return performanceB(salary);
    }
    if(level === 'C') {
        return performanceC(salary);
    }
};
// 调用如下
console.log(calculateBouns('A',4500)); // 18000

复用性差

函数体比较庞大,其内包含了很多if-else语句。如果在其他的地方也有类似这样的算法的话,但是规则不一样,我们这些代码不能通用。

缺乏弹性

假如还有D等级的话,那么我们需要添加D算法的同时,在calculateBouns 函数体内添加判断等级D的if语句;

或者想把绩效A的奖金系数改为 5,那我们必须深入 calculateBonus 函数的内部实现,这是违反开放封闭原则的。

我们根据策略模式封装复用的思想,进行改写。

ES5

 // 改为策略模式 分成两个函数来写
const strategy = {
  'C' : function(experience){// C算法
    return 4*experience
  },
  'A' : function(experience){// A算法
    return 3*experience
  },
  'B' : function(experience){// B算法
    return 2*experience
  }
}

// getExperience可以复用
function getExperience(strategy, level, experience){
  return (level in strategy) ? strategy[level](experience) : experience
}
var C = getExperience(strategy, 'C', 100)
var a = getExperience(strategy, 'A', 100)

ES6

var performanceA = function () {};
performanceA.prototype.calculate = function (salary) {// A算法
  return salary * 3;
};
var performanceB = function () {};
performanceB.prototype.calculate = function (salary) {// B算法
  return salary * 2;
};
var performanceC = function () {};
performanceC.prototype.calculate = function (salary) {// C算法
  return salary * 4;
};

//接下来定义奖金类Bonus:
class Bonus {
  constructor() {
    this.salary = null; 	// 原始工资
  	this.strategy = null; // 绩效等级对应的策略对象
  }
  setSalary(salary) {// 设置员工的原始工资
    this.salary = salary; 
  }
  setStrategy(strategy) {// 设置员工绩效等级对应的策略对象
    this.strategy = strategy; 
  }
  getBonus() { // 取得奖金数额
    return this.strategy.calculate(this.salary); // 把计算奖金的操作委托给对应的策略对象
  }
}

var bonus = new Bonus();
bonus.setSalary(10000);// 设置原始工资

bonus.setStrategy(new performanceS()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:40000
bonus.setStrategy(new performanceA()); // 设置策略对象
console.log(bonus.getBonus()); // 输出:30000

策略模式的应用场景?

应用实例

  • jquery插件表单验证代码是策略模式的思想封装的
  • Elementui中,基于async-validator库,只需要通过rule属性传入约定的验证规则,即可校验。方便快捷,可复用

项目实战

验证表单

比如有以下几条逻辑:

  • 用户名不能为空
  • 密码长度不能小于6位。
  • 手机号码不能为空、且必须符合格式。
/**HTML结构**/
<form action="/" class="form">
  请输入用户名称:<input type="text" name="username">
  请输入用户密码:<input type="password" name="password">
  请输入用户电话:<input type="text" name="phoneNumber" />
  <button>submit</button>
</form>
  
/**首先定义校验规则**/
const strategies = {
    noEmpty: function(value, errMsg){ // 非空
      if(value === ''){
        return errMsg
      }
    },
    minLength: function(value, length, errMsg){ // 最小长度
      if(!value || value.length < length){
        return errMsg
      }
    },
    maxLength: function(value, errMsg){// 最大长度
      if (value.length > length) {
        return errorMsg;
      }
    }
  }
  
/**接着设置验证器(策略模式)**/
var Validator = function(strategies){// 创建验证器
    this.strategies = strategies // 校验规则
    this.cache = [] // 存储校验规则
  }
Validator.prototype.add = function(dom, rules){// 添加校验规则
    rules.forEach(item => {
      this.cache.push(() => {
        let value = dom.value 					// 把input 的value 添加进参数列表
        let arr = item.rule.split(':') 	// 把strategy 和参数分开
        let name = arr.shift() 					// 用户挑选的strategy
        let params = [value, ...arr, item.errMsg]
        return this.strategies[name].apply(dom, params)// apply保证上下文一致
      })
    })
  }
Validator.prototype.validate = function(dom, rules, errMsg){// 校验结果
    for(let i = 0, validateFun; validateFun = this.cache[i++];){// 遍历cache里面的校验函数
      const message = validateFun() // 开始效验 并取得效验后的返回信息
      if(message) return message 		// 如果有确切的返回值,说明校验没有通过
    }
  }
  
/**提交验证**/
var form = document.querySelector("form")
form.onsubmit = function(event){	// 提交表单
    event.preventDefault() 
    const message = validate()		// 判断验证结果
    if(message){
      console.log("验证未通过!",message)
    }else{
      console.log("验证通过!")
    }
  }
    
function validate(){	// 校验函数
    const validator = new Validator(strategies)	// 实例验证器
      // 添加验证规则
      validator.add(form.username, 
                    [{rule: 'noEmpty',errMsg: '用户名不能为空!'},{rule: 'minLength:3',errMsg: '用户名长度大于3!'}])
      validator.add(form.password, 
                    [{rule: 'minLength:6',errMsg: '密码长度大于6!'},{rule: 'maxLength:10',errMsg: '密码最大长度为10!'}])
      return validator.validate()// 进行校验,并返回结果
  }

总结

策略模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点。实现的方法为先判断实例存在与否,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。

优缺点

优:

  • 策略模式利用组合、委托和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的strategy中,使得它们易于切换,易于理解,易于扩展。
  • 策略模式中的算法也可以复用在系统的其他地方,从而避免许多重复的复制粘贴工作。
  • 在策略模式中利用组合和委托来让 Context 拥有执行算法的能力,这也是继承的一种更轻便的替代方案。

缺:

  • 增加许多策略类或者策略对象,但实际上这比把它们负责的 逻辑堆砌在 Context 中要好。
  • 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点, 这样才能选择一个合适的 strategy。

最后一句
学习心得!若有不正,还望斧正。希望掘友们不要吝啬对我的建议。

原文链接:https://juejin.cn/post/7218362171965358139 作者:沉曦

(0)
上一篇 2023年4月5日 下午4:00
下一篇 2023年4月5日 下午4:10

相关推荐

发表评论

登录后才能评论