设计模式之策略模式

Posted by franki on November 1, 2018

strategy

策略模式

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

目的是将算法的使用和算法的实现分离开来;一个基本策略模式的程序至少有两部分组成,一部分是策略类,策略类封装了具体的算法,并负责具体的计算过程。第二部分是环境类Context,Context接收请求,随后把请求委托给某个策略类。要做到这点,需要Context要维持对某个策略对象的引用。

模仿静态语言的传统面向对象的实现

// 策略类S 算法实现
var performanceS = function(){};
performanceS.prototype.calculate = function(salary){return salary*4;};

// 策略类A 算法实现
var performanceA = function(){};
performanceA.prototype.calculate = function(salary){return salary*3;};

// 策略类B 算法实现
var performanceB = function(){};
performanceB.prototype.calculate = function(salary) {return salary*2;};

// Context 接收请求实现
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())

bonus.setStrategy(new performanceA());
console.log(bonus.getBonus())

JS 版本的策略模式 如上,传统的策略对象都是基于某个策略类创建的,但是js中,函数也是对象,所以可以直接将策略定义为函数

// 具体的策略及其计算过程用函数实现,然后直接封装在一个对象中
var strategies = {
    S: function(salary){
        return salary * 4;
    },
    A: function(salary){
        return salary * 3;
    },
    B: function(salary){
        return salary * 2;
    }
};

// Context 接收用户请求,则也是通过定义一个对象去返回具体的结果
var calculateBonus = function(level, salary) {
    return strategies[level](salary);
}

calculateBonus(S, 20000); // 80000

利用策略模式写一个动画类

const tween = {
    linear: (t, b, c, d) => {
        return (c * t) / d + b;
    },
    easeIn: (t, b, c, d) => {
        return c * (t /= d) * t + b;
    },
    strongEaseIn: (t, b, c, d) => {
        return c * (t /= d) * t * t * t * t + b;
    },
    strongEaseOut: (t, b, c, d) => {
        return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
    },
    sineaseIn: (t, b, c, d) => {
        return c * (t /= d) * t * t + b;
    },
    sineaseOut: (t, b, c, d) => {
        return c * ((t = t / d - 1) * t * t + 1) + b;
    }
};

const Animate = function(dom) {
    this.dom = dom;
    this.startTime = 0;
    this.startPos = 0;
    this.endPos = 0;
    this.propertyName = null;
    this.easing = null;
    this.duration = null;
};

Animate.prototype.start = function(propertyName, endPos, duration, easing) {
    this.startTime = +new Date();
    this.startPos = this.dom.getBoundingClientRect()[propertyName];
    this.propertyName = propertyName;
    this.endPos = endPos;
    this.duration = duration;
    this.easing = tween[easing];

    const self = this;
    const timeId = setInterval(() => {
        if (self.step() === false) {
            clearInterval(timeId);
        }
    }, 19);
};

Animate.prototype.step = function() {
    const t = +new Date();
    if (t >= this.startTime + this.duration) {
        this.update(this.endPos);
        return false;
    }
    var pos = this.easing(
    t - this.startTime,
    this.startPos,
    this.endPos - this.startPos,
    this.duration
    );
    this.update(pos);
};

Animate.prototype.update = function(pos) {
    this.dom.style[this.propertyName] = pos + "px";
};

const div = document.getElementById("haha");
console.log(div);
const animate = new Animate(div);
animate.start("right", 100, 1000, "strongEaseOut");

策略模式应用于表单提交

// 假设有这样的一个表单
<form id="registerForm" method="post">
    请输入用户名: <input type="text" name="userName" />
    <br />
    请输入密码: <input type="text" name="password" />
    <br />
    请输入手机号: <input type="text" name="phoneNumber" />
    <br />
    <button>提交</button>
</form>


// 策略算法具体实现(具体业务)
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;
        }
    }
};

// Context 接收客户请求,给出结果
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 msg = validatorFunc();
        if (msg) {
            return msg;
        }
    }
};

// 调用校验函数(context),实例化校验类生成一个校验实例,添加对应的参数规则
var validataFunc = function(registerForm) {
    var validator = new Validator();

    validator.add(
        registerForm.userName,
        [
            {strategy: 'isNonEmpty', errorMsg: "用户名不能为空"},
            { strategy: "minLength:10", errorMsg: "用户名长度不能少于10位"}
        ]
    );
    validator.add(
        registerForm.password,
        [{strategy: "minLength:3", errorMsg: "密码长度不能少于3位" }]
    );
    validator.add(
        registerForm.phoneNumber,
        [{strategy: "isMobile", errorMsg: "手机号码不正确" }]
    );


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

// 真正使用仅有validataFunc方法足以完成校验
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function() {
    var errorMsg = validataFunc(registerForm);
    if (errorMsg) {
        alert(errorMsg);
        return false;
    }
};

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

缺点: 1 使用策略模式会在程序中增加许多策略类或者策略对象 2 要使用策略模式,必须了解所有的策略,才能选择一个合适的策略,这是违反最少知识原则的

用javascript的函数的形式实现策略模式,这是一种隐式的策略模式,请看利用高阶函数来封装不同的行为,并且把它传递到另一个函数中。当我们对这些函数发出调用信息时, 这些函数会返回不同的执行结果。

var S = function( salary ){
    return salary * 4;
};

var A = function( salary ){
    return salary * 3;
};

var B = function( salary ){
    return salary * 2;
};
var calculateBonus = function( func, salary ){
    return func( salary );
}

calculateBonus( S, 10000 ); // 输出:40000

总结: 本章我们既提供了接近传统面向对象语言的策略模式实现, 也提供了更适合 JavaScript 语言 的策略模式版本。在 JavaScript 语言的策略模式中,策略类往往被函数所代替, 这时策略模式就 成为一种“隐形”的模式。尽管这样,从头到 尾地了解策略模式,不仅可以让我们对该模式有更 加透彻的 了解,也可以使我们明白使用函数的好处。