「设计模式」策略模式🍈

326 阅读4分钟

这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战

策略模式的定义

定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

策略模式的目的就是将算法的使用与算法的实现分离开来。一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。第二个部分是环境类 Context,Context 接受客户的请求,随后把请求委托给某一个策略类。

结合上面的语句,说得更详细点,策略模式的定义是:定义一系列的算法,把它们各自封装成策略类,算法被封装在策略类内部的方法里。在客户对 Context 发起请求的时候,Context 总是把请求委托给这些策略对象中间的某一个进行计算。

策略模式的应用场景

关于策略模式的应用场景其实算是蛮广泛的,如果要实现的某一个功能有多种方案选择,那么它就适合用策略模式来实现。

在这里我们列出几个比较普遍的应用场景并加以解释:

根据员工的工资基数 M 和年底绩效 (S、A...) 来决定年终奖

针对单个员工来说,工资基数是固定的,年底绩效根据拿到的等级来对应各个等级的年终奖算法,在这里策略类就是各个绩效对应年终奖计算公式的方法组合,而环境类则是根据员工的绩效来选择相应计算公式。 使用策略模式实现缓动动画 策略类是不同的动画运动规则(线性、缓入缓出等),而环境类则包含负责通用的启动、更新等方法,各个方法根据需要调用运动规则。 表单校验 策略类是不同的校验方法组合,环境类是则是提供数据缓存和校验步骤等环节。

策略模式的优缺点

优点:

  • 利用组合、委托和多态等技术和思想,可以有效避免多重判断语句(消灭多重if-else)
  • 提供了对开闭原则的完美支持,将算法封装在独立的策略类中,使得它们易于切换、易于理解、易于扩展
  • 策略模式中的算法也可以复用在其他地方,从而避免许多重复的复制粘贴工作
  • 利用组合和委托来让语境 Context 拥有执行算法的能力 缺点:
  • 使用策略模式会在程序中增加许多策略类,简单来说就是增加代码量,但其实比把这部分逻辑堆砌在 Context 中要好
  • 要使用策略模式,必须了解所有的策略方法,只有了解了,才能在使用时选择最合适的。此时策略类必须暴露它的所有实现,这是违反最少知识原则的。

策略模式的实现原理分析

接下来我们分析一下策略模式应用于表单校验的实现思路:

  • 明确一下校验逻辑(用户名不能为空、密码不少于6位);
  • 根据校验逻辑封装策略类
  • 接下来需要实现环境类,为了更好地编写环境类,我们提前了解用户是如果向环境类发送请求的,写出调用代码
  • 根据上一步的调用来实现环境类

具体实现的代码

先写出校验用的 html 表单代码

<form action="http:// xxx.com/register" id="registerForm" method="post">
    请输入用户名:<input type="text" name="userName"/ >
    请输入密码:<input type="text" name="password"/ >
    <button>提交</button>
</form>

为了更好的理解策略模式带来的好处,这里列出使用策略模式前后的代码作为对比。

使用策略模式前:

var form = document.getElementById('registerForm');

form.onsubmit = function() {
    // 每次添加校验规则直接加在这里,规则越多,这里的 if 就更多
    if(form.userName.value === '') {
        alert('用户名不能为空');
        return false;
    }
    if(form.password.value.length < 6) {
        alert('密码长度不能少于 6 位');
        return false;
    }
}

使用策略模式后:

// 实现策略类
var strategies = {
    isNonEmpty: function(v, msg) {
        if(v === '') return msg;
    },
    minLength: function(v, l, msg) {
        if(v.length < l) return msg;
    }
};
// 环境类的实现
var Context = function(){
    this.cache = [];
};
Context.prototype.add = function(d, method, msg) {
    var ary = method.split(':'); // 考虑验证密码长度的情况
    this.cache.push(function() {
       var strategy = ary.shift(); 
       ary.unshift(d.value);
       ary.push(msg);
       // 返回策略类相应的调用结果
       return strategies[strategy].apply(d, ary);
    });
};
Context.prototype.start = function() {
    // 循环对配置的规则进行验证
    for(var i = 0, func; func = this.cache[i++]) {
        var msg = func();
        if(msg) return msg;
    }
};
// 用户调用时
var form = document.getElementById('registerForm');
var vFunc = function() {
    var validator = new Context();
    
    validator.add(form.userName, 'isNonEmpty', '用户名不能为空');
    validator.add(form.password, 'minLength:6', '密码长度不能少于 6 位');
    
    var msg = validator.start();
    return msg;
};
form.onsubmit = function() {
    var msg = vFunc();
    if(msg) return msg;
};