JavaScript设计模式——策略模式

94 阅读3分钟

五、策略模式(Strategy Pattern)

1.概念介绍

策略模式(Strategy Pattern):封装一系列策略方法,让外部调用者在使用相同接口时,可以选择不同策略方法。它的目的是为了将策略方法的使用与具体方法的实现分离开来

策略模式通常会有两部分组成,一部分是策略类,它负责实现通用的算法,另一部分是环境类,它用户接收客户端请求并委托给策略类。

2.优缺点

2.1 优点

  • 有效地避免多重条件选择语句;

  • 支持开闭原则,将算法独立封装,使得更加便于切换、理解和扩展;

  • 更加便于代码复用;

2.2 缺点

  • 策略类会增多;

  • 所有策略类都需要对外暴露;

3.基本案例

我们可以很简单的将策略和算法直接做映射:

let add = {
    "add3" : (num) => num + 3,
    "add5" : (num) => num + 5,
    "add10": (num) => num + 10,
}
let demo = (type, num) => add[type](num);
console.log(demo('add3', 10));  // 13
console.log(demo('add10', 12)); // 22

然后我们再把每个策略的算法抽出来:

let fun3  = (num) => num + 3;
let fun5  = (num) => num + 5;
let fun10 = (num) => num + 10;
let add = {
    "add3" : (num) => fun3(num),
    "add5" : (num) => fun5(num),
    "add10": (num) => fun10(num),
}
let demo = (type, num) => add[type](num);
console.log(demo('add3', 10));  // 13
console.log(demo('add10', 12)); // 22

4.表单验证案例

我们需要使用策略模式,实现一个处理表单验证的方法,无论表单的具体类型是什么都会调用验证方法。我们需要让验证器能选择最佳的策略来处理任务,并将具体的验证数据委托给适当算法。

我们假设需要验证下面的表单数据的有效性:

let data = {
    name    : 'Jian',
    age     : 'unknown',
    nickname: 'Gao',
}

这里需要先配置验证器,对表单数据中不同的数据使用不同的算法:

validator.config = {
    name    : 'isNonEmpty',
    age     : 'isNumber',
    nickname: 'isAlphaNum',
}

并且我们需要将验证的错误信息打印到控制台:

validator.validate(data);
if(validator.hasErrors()){
    console.log(validator.msg.join('\n'));
}

接下来我们才要实现 validator中具体的验证算法,他们都有一个相同接口 validator.types,提供 validate()方法和 instructions帮助信息:

// 非空值检查
validator.types.isNonEmpty = {
    validate: function(value){
        return value !== '';
    }
    instructions: '该值不能为空'
}

// 数值类型检查
validator.types.isNumber = {
    validate: function(value){
        return !isNaN(value);
    }
    instructions: '该值只能是数字'
}

// 检查是否只包含数字和字母
validator.types.isAlphaNum = {
    validate: function(value){
        return !/[^a-z0-9]/i.test(value);
    }
    instructions: '该值只能包含数字和字母,且不包含特殊字符'
}

最后就是要实现最核心的 validator对象:

let validator = {
    types: {}, // 所有可用的检查
    msg:[],    // 当前验证的错误信息
    config:{}, // 验证配置
    validate: function(data){ // 接口方法
        let type, checker, result;
        this.msg = []; // 清空错误信息
        for(let k in data){
            if(data.hasOwnProperty(k)){
                type = this.config[k];
                checker = this.types[type];
                if(!type) continue;  // 不存在类型 则 不需要验证
                if(!checker){
                    throw {
                        name: '验证失败',
                        msg: `不能验证类型:${type}`
                    }
                }
                result = checker.validate(data[k]);
                if(!result){
                    this.msg.push(`无效的值:${k}${checker.instructions}`);
                }
            }
        }
        return this.hasErrors();
    }
    hasErrors: function(){
        return this.msg.length != 0;
    }
}

总结这个案例,我们可以看出 validator对象是通用的,需要增强 validator对象的方法只需添加更多的类型检查,后续针对每个新的用例,只需配置验证器和运行 validator()方法就可以。

5.小结

日常开发的时候,还是需要根据实际情况来选择设计模式,而不能为了设计模式而去设计模式。通过上面的学习,我们使用策略模式来避免多重条件判断,并且通过开闭原则来封装方法。我们应该多在开发中,逐渐积累自己的开发工具库,便于以后使用。

参考资料

  1. 《JavaScript Patterns》

  2. 《JavaScript 高级应用程序设计》

本文使用 文章同步助手 同步