设计模式——策略模式(JS实现)

450 阅读3分钟

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

使用策略模式实现计算奖金

奖金的计算方式:绩效为S的人奖金为工资的4倍,绩效为A的奖金为工资的3倍,绩效为B的奖金为工资的2倍。

    //每种绩效的计算规则封装到对应的策略类
    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);
    }

对于上面的解释:定义一系列的算法,把它们封装成策略类,算法被封装在策略类内部的方法里,在客户对Context发起请求的时候,Context总是把请求委托给这些策略对象中的某一个进行计算。

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实现策略模式。

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
更广义的”算法“

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

用策略模式实现表单校验

编辑一个注册的页面,在点击注册按钮之前,有如下几条校验规则:

  • 用户名不能为空。
  • 密码长度不能少于6位。
  • 手机号必须符合格式。
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<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>
</body>
<script>
    /********策略类*****/
    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;
            }
        }
    }i++
    /*************Validator类************/
    var Validator = function(){
        this.cache = [];
    }
    Validator.prototype.add = function(dom,rules){
        var self = this;
        for(var i = 0; rule;rule = rules[i++]){
            (function(rule){
                var strategyAry = rule.strategy.split(':');
                var errorMsg = rule.errorMsg;;
                self.cache.push(function(){
                    var strategy = strategyAry.shift();
                    strategyAry.unshift(dom.value);
                    strategyAry.push(errorMsg);
                    return strategies[strategy].apply(dom,strategyAry);
                });
            })(rule)
        }
    };

    Validator.prototype.start = function(){
        for(var i = 0; ValidatorFunc;ValidatorFunc = this.cache[i++];){
            var errorMsg = ValidatorFunc();
            if(errorMsg){
                return errorMsg;
            }
        }
    }

    /**************客户调用代码******************/
    var registerForm = document.getElementById('registerForm');
    var validataFunc = function(){
        var validator = new Validator();

        validator.add(registerForm.userName,[{strategy:'isNonEmpty',errorMsg:'用户名不能为空'}
        ,{strategy:'minLength:10',errorMsg:'用户名长度不能小于10位'}]);

        validator.add(registerForm.password,[{strategy:'minLength:6',errorMsg:'密码长度不能小于6位'}]);

        validator.add(registerForm.phoneNumber,[{strategy:'isMobile',errorMsg:'手机号码格式不对'}]);

        var errorMsg = validator.start();
        return errorMsg;
    }

    registerForm.onsubmit = function(){
        var errorMsg = validataFunc();
        if(errorMsg){
            alert(errorMsg);
            return false;
        }
    }

</script>
</html>
策略模式的优缺点

策略模式的优点:

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