JavaScript设计模式———策略模式

策略模式

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

例子:

计算奖金:根据绩效不同,年终奖金计算不同

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

模拟传统面向对象语言的实现。(及对象由类class生产)

// 不同策略类
var performanceS = function(){};
performanceS.prototype.calculate = function( salary ){
return salary * 4;
};
var performanceA = function(){};
performanceA.prototype.calculate = function( salary ){
return salary * 3;
};
var performanceB = function(){};
performanceB.prototype.calculate = function( salary ){
return salary * 2;
};

// 接下来定义奖金类 Bonus:
var Bonus = function(){
this.salary = null; // 原始工资
this.strategy = null; // 绩效等级对应的策略对象
};
Bonus.prototype.setSalary = function( salary ){
this.salary = salary; // 设置员工的原始工资
};
Bonus.prototype.setStrategy = function( strategy ){
this.strategy = strategy; // 设置员工绩效等级对应的策略对象
};
Bonus.prototype.getBonus = function(){ // 取得奖金数额
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

JavaScript 版本的实现。(及函数即为对象,不需要建立类)

var strategies = {
    "S": function(salary) {
        return salary * 4;
    },
    "A": function(salary) {
        return salary * 3;
    },
    "B": function(salary) {
        return salary * 2;
    }
};
var calculateBonus = function(level, salary) {
    return strategies[level](salary);
};
console.log(calculateBonus('S', 20000)); // 输出: 80000
console.log(calculateBonus('A', 10000)); // 输出: 30000

更广义的“算法”

从定义上看,策略模式就是用来封装算法的。但如果把策略模式仅仅用来封装算法,未免有一点大材小用。在实际开发中,我们通常会把算法的含义扩散开来,使策略模式也可以用来封装一系列的“业务规则”只要这些业务规则指向的目标一致,并且可以被替换使用,我们就可以用策略模式来封装它们。

以表单验证为例子:

普通方式:

<html>
<body>
    <form action="http:// xxx.com/register" id="registerForm" method="post">
        请输入用户名: <input type="text" name="userName" /> 请输入密码: <input type="text" name="password" /> 请输入手机号码: <input type="text" name="phoneNumber" />
        <button>提交</button>
    </form>
    <script>
        var 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>

策略模式:

var 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;
        }
    }
};

var validataFunc = function() {
    var validator = new Validator(); // 创建一个 validator 对象
    /***************添加一些校验规则****************/
    validator.add(registerForm.userName, 'isNonEmpty', '用户名不能为空');
    validator.add(registerForm.password, 'minLength:6', '密码长度不能少于 6 位');
    validator.add(registerForm.phoneNumber, 'isMobile', '手机号码格式不正确');
    var errorMsg = validator.start(); // 获得校验结果
    return errorMsg; // 返回校验结果
}
var registerForm = document.getElementById('registerForm');
registerForm.onsubmit = function() {
    var errorMsg = validataFunc(); // 如果 errorMsg 有确切的返回值,说明未通过校验
    if (errorMsg) {
        alert(errorMsg);
        return false; // 阻止表单提交
    }
};

var Validator = function() {
    this.cache = []; // 保存校验规则
};
Validator.prototype.add = function(dom, rule, errorMsg) {
    var ary = rule.split(':'); // 把 strategy 和参数分开
    this.cache.push(function() { // 把校验的步骤用空函数包装起来,并且放入 cache
        var strategy = ary.shift(); // 用户挑选的 strategy
        ary.unshift(dom.value); // 把 input 的 value 添加进参数列表
        ary.push(errorMsg); // 把 errorMsg 添加进参数列表
        return strategies[strategy].apply(dom, ary);
    });
};
Validator.prototype.start = function() {
    for (var i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
        var msg = validatorFunc(); // 开始校验,并取得校验后的返回信息
        if (msg) { // 如果有确切的返回值,说明校验没有通过
            return msg;
        }
    }
};

一等函数对象与策略模式

在函数作为一等对象的语言中,策略模式是隐形的

在 JavaScript 中,除了使用类来封装算法和行为之外,使用函数当然也是一种选择。这些“算法”可以被封装到函数中并且四处传递,也就是我们常说的高阶函数。实际上在 JavaScript 这种将函数作为一等对象的语言里,**策略模式已经融入到了语言本身当中,我们经常用高阶函数来封装不同的行为,并且把它传递到另一个函数中。**当我们对这些函数发出“调用”的消息时,不同的函数会返回不同的执行结果。在JavaScript 中,“函数对象的多态性”来得更加简单。

总结:
优点:

  • 避免多重条件选择语句:策略模式利用组合委托多态等技术和思想,可以有效地避免多重条件选择语句。
  • 复用性好:策略模式提供了对开放—封闭原则的完美支持,将算法封装在独立的 strategy 中,使得它们易于切换,易于理解,易于扩展。

缺点

  • 增加许多策略类或者策略对象: 但实际上这比把它们负责的逻辑堆砌在 Context 中要好。
  • 违反最少知道原则: 要使用策略模式,必须了解所有的 strategy,必须了解各个 strategy 之间的不同点,这样才能选择一个合适的 strategy。比如,我们要选择一种合适的旅游出行路线,必须先了解选择飞机、火车、自行车等方案的细节。此时 strategy 要向客户暴露它的所有实现

在 JavaScript 语言的策略模式中,策略类往往被函数所代替,这时策略模式就成为一种隐形的模式

posted @ 2020-04-30 00:12  CD、小月  阅读(6)  评论(0编辑  收藏  举报  来源