策略模式

205 阅读2分钟

什么是策略模式?

策略模式(Strategy Pattern) 是一种行为设计模式。它定义一系列的策略算法,把它们一个个封装起来,每个策略都可以根据当前场景相互替换,从而将策略的实现使用进行分离。 一个基于策略模式的程序至少由两部分组成:

  • 策略类(不变的内容),策略类封装了具体的算法,并负责具体的计算过程
  • 环境类(变化的内容),Context,Context 接受客户的请求,随后 把请求委托给某一个策略类

策略模式的优点

  • 策略模式利用组合,委托等技术和思想,有效的避免很多if条件语句
  • 策略模式提供了开放-封闭原则,使代码更容易理解和扩展
  • 策略模式中的代码可以复用

关于开放封闭原则JavaScript五大原则之开闭原则

使用

我们用一个简单的例子引入

如果我们要对实现两个数的加减乘除操作,一般我们会写出下面的代码

function Calculate(command, num1, num2) {
    if (command === 'add') {
        return num1 + num2
    } else if (command === 'sub') {
        return num1 - num2
    } else if (command === 'mul') {
        return num1 * num2
    } else if (command === 'div') {
        return num1 / num2
    }
}

这样一看, 会有非常多的if-else , 这是在判断语句不多的情况下使用是这样的, 我们可以先借这个例子引入策略模式

  • 首先我们定义策略类,即 实现加减乘除操作
  • 我们再定义一个环境类, 用于把操作委托给某一个策略类

定义策略类

// 定义策略类
class Add {
    execute(num1, num2) {
        return num1 + num2
    }
}

class Sub {
    execute(num1, num2) {
        return num1 - num2
    }
}

class Mul {
    execute(num1, num2) {
        return num1 * num2
    }
}

class Div {
    execute(num1, num2) {
        return num1 / num2
    }
}
// 定义一个环境类
class Claculate {
    constructor() {
        this.strategy = null
    }
    setStratrgy(strategy) {
        this.strategy = strategy    // 更改策略对象, 即要实施加减乘除中的什么操作
    }
    calculateResult(num1, num2) {   // 委托策略类
        return this.strategy.execute(num1, num2)
    }
}
const calculate = new Claculate()
calculate.setStratrgy(new Add())    // 更改策略对象
console.log(calculate.calculateResult(6, 3));    // 调用该车略对象的execute函数
calculate.setStratrgy(new Sub())    // 更改策略对象
console.log(calculate.calculateResult(6, 3));    // 调用该车略对象的execute函数

代码运行结果如图:

image.png

实战

在表单验证的时候,我们经常会写出这样的代码

let registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    if(registerForm.userName.value === '') {
        alert('用户名不能为空');
        return;
    }
    if(registerForm.password.value.length < 6) {
        alert("密码的长度不能小于6位");
        return;
    }
    if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
        alert("手机号码格式不正确");
        return;
    }
}

从上述可有看到,函数内部包含过多if...else,并且后续改正的时候,需要在函数内部添加逻辑,违反了开放封闭原则

而如果使用策略模式,就是先定义一系列算法,把它们一个个封装起来,将不变的部分和变化的部分隔开,如下:

定义表单

    <form action="javascript:;" class="form">
        请输入用户名称:<input type="text" name="userName" />
        请输入用户密码:<input type="password" name="password">
        请输入用户电话:<input type="text" name="phoneNumber" />
        <button class="onSubmit">submit</button>
    </form>

定义策略类第一步确定不变的内容,即策略规则对象,如下:

// 第一步,定义策略类,策略类封装了具体的算法,并负责具体的计算过程,确定不变的内容,即策略规则对象,这里指校验规则
const strategies = {
    // 不能为空
    isNotEmpty: function (value, errorMsg) {
        if (value === '') {
            return errorMsg;
        }
    },
    // 限制最小长度
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    // 手机号码格式
    mobileFormat: function (value, errorMsg) {
        if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    }
}

定义环境类然后找出变的地方,作为环境类context,负责接收用户的要求并委托给策略规则对象,如下Validator类:

function Validator() {
    this.ache = []          // 保存效验结果
    this.strategy = null  // 对应哪个策略
}

// 添加哪些表单需要用什么样的校验规则去校验,以及错误信息是什么
Validator.prototype.add = function (dom, rules, errMsg) {
    this.ache.push(function () {
        // 当 rules 为 minLength:6 
        let str = rules.split(':')
        // 更改策略对象, 即要去校验哪一条规则,如 isNotEmpty, minLength
        this.strategy = str.shift()
        str.unshift(dom.value)
        str.push(errMsg)
        // 最后str 形如 [registerForm.password.value ,6, '密码长度不能小于6位'] 
        // 或 [ registerForm.userName.value ,'用户名不能为空']
        return strategies[this.strategy].apply(dom, str)    // 返回校验结果
    })
}

// 开始校验
Validator.prototype.star = function () {
    for (let i = 0, validator; validator = this.ache[i++];) {  // 遍历cache里面的校验函数
        let errMsg = validator()                 // 开始效验 并取得效验后的返回信息
        if (errMsg) return errMsg               // 如果有确切的返回值,说明校验没有通过
    }
}

通过validator.add方法添加校验规则和错误信息提示,使用如下:


function validatorFn() {
    const validator = new Validator()
    validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空')
    validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位')
    validator.add(registerForm.phoneNumber, 'mobileFormat', '手机号码格式不正确')
    const errMsg = validator.star()
    return errMsg
}
const btn = document.querySelector('.onSubmit')
btn.addEventListener('click', () => {
    const msg = validatorFn()
    msg && (console.log(msg))
})

最后结果 用户名为空时

image.png

密码长度小于6位

image.png

手机号码格式不正确

image.png

完整代码

<!DOCTYPE html>
<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>
    <form action="javascript:;" class="form">
        请输入用户名称:<input type="text" name="userName" />
        请输入用户密码:<input type="password" name="password">
        请输入用户电话:<input type="text" name="phoneNumber" />
        <button class="onSubmit">submit</button>
    </form>
</head>

<body>
    <script>
        const registerForm = document.querySelector('.form')
        // 第一步,定义策略类,策略类封装了具体的算法,并负责具体的计算过程,确定不变的内容,即策略规则对象,这里指校验规则
        const strategies = {
            // 不能为空
            isNotEmpty: function (value, errorMsg) {
                if (value === '') {
                    return errorMsg;
                }
            },
            // 限制最小长度
            minLength: function (value, length, errorMsg) {
                if (value.length < length) {
                    return errorMsg;
                }
            },
            // 手机号码格式
            mobileFormat: function (value, errorMsg) {
                if (!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
                    return errorMsg;
                }
            }
        }

        function Validator() {
            this.ache = []        // 保存效验结果
            this.strategy = null  // 对应哪个策略
        }

        // 添加哪些表单需要用什么样的校验规则去校验,以及错误信息是什么
        Validator.prototype.add = function (dom, rules, errMsg) {
            this.ache.push(function () {
                // 当 rules 为 minLength:6 
                let str = rules.split(':')
                // 获取对应策略
                this.strategy = str.shift()
                str.unshift(dom.value)
                str.push(errMsg)
                // 最后str 形如 [registerForm.password.value ,6,'密码长度不能小于6位'] 或 [ registerForm.userName.value ,'用户名不能为空']
                return strategies[this.strategy].apply(dom, str)    // 返回校验结果
            })
        }

        // 开始校验
        Validator.prototype.star = function () {
            for (let i = 0, validator; validator = this.ache[i++];) {  // 遍历cache里面的校验函数
                let errMsg = validator()                               // 开始效验 并取得效验后的返回信息
                if (errMsg) return errMsg                              // 如果有确切的返回值,说明校验没有通过
            }
        }

        function validatorFn() {
            const validator = new Validator()
            validator.add(registerForm.userName, 'isNotEmpty', '用户名不能为空')
            validator.add(registerForm.password, 'minLength:6', '密码长度不能小于6位')
            validator.add(registerForm.phoneNumber, 'mobileFormat', '手机号码格式不正确')
            const errMsg = validator.star()
            return errMsg
        }

        const btn = document.querySelector('.onSubmit')
        btn.addEventListener('click', () => {
            const msg = validatorFn()
            msg && (console.log(msg))
        })
    </script>
</body>
</html>