《JavaScript设计模式与开发实践》——学习笔记(策略模式)

120 阅读2分钟

定义

策略模式的目的就是将算法的使用与算法的实现分离开来。——《JavaScript设计模式与开发实践》

其由两部分组成:

1、多个策略类:封装了具体的算法

2、环境类:负责根据用户请求,调用特点的策略类,并将该策略类的执行结果返回给客户。

策略模式可以去除掉大量if-else语句,使得代码更易于切换、理解、扩展。

适用场景

1、需要多个if-else进行区分处理。

2、表单验证(扩展)。

实现示例

const strategies = {
  'S': (salary) => {
      return salary * 4;
  },
  'A': (salary) => {
      return salary * 3;
  },
  'B': (salary) => {
      return salary * 2;
  },
  'default': (salary) => {
      return salary;
  }
};
const calculateBonus = (level, salary) => {
  const fn = strategies[level] || strategies['default'];
  return fn.apply(strategies, salary);
};

console.log(calculateBonus('S', 20000)); // 输出:80000
console.log(calculateBonus('A', 10000)); // 输出:30000

项目中的运用

原始版本

function getMainView () {
    const mainType = this.get('mainType'); //icfiles netdisk
    if (mainType == 'icfiles') {
        return MainIcfiles
    } else if (mainType == 'netdisk') {
        return MainNetdisk;
    } else if (mainType == 'card') {
        return MainCard;
    } else if(mainType == 'crm') {
        return MainCRM;
    } else if(mainType == 'email') {
        return MainEmail;
    } else {
        return MainImage;
    }
};

很显然getMainView方法中充斥着if-else,可以利用策略模式进行重构。

改进版本

function getMainView () {
    const strategies = {
      'icfiles': MainIcfiles,
      'netdisk': MainNetdisk,
      'card': MainCard,
      'crm': MainCRM,
      'email': MainEmail,
      'default': MainImage
    };
    getMainView = function () {
    	const mainType = this.get('mainType'); //icfiles netdisk
        return strategies[mainType] || strategies['default'];
    }
    getMainView();
};

利用策略模式重构后的代码看上去清爽很多,而且不会因为后续各种判断条件的膨胀而影响性能。

扩展运用

表单验证

策略模式不仅仅只是对算法的“封装”,也可以用来封装“业务规则”,《JavaScript设计模式与开发实践》书中的例子就是“策略模式”在表单验证中的运用。

以下代码是我根据书中示例代码,加上自己的理解进行重写得到的。

/***********************策略对象**************************/
const strategies = {
    isNonEmpty: ( value, errorMsg ) => {
        if (value === '') {
            return errorMsg;
        }
    },
    minLength: (value, length, errorMsg) => {
        if (value.length < length) {
            return errorMsg;
        }
    },
    isMobile: (value, errorMsg) => {
        if (!/(^1[3|5|8][0-9]{9}$)/.test( value )) {
            return errorMsg;
        }
    }
};
/***********************Validator 类**************************/
// 用与管理各表单字段的校验策略
class Validator {
    constructor() {
        this.cache = [];
    }
    add (dom, rules) {
        const self = this;
        if (!Array.isArray(rules)) {
            rules = [rules];
        }
        rules.forEach(rule => {
            // strategy是一个可以设置参数的校验规则如 minLength:5 表示最小长度为5
            const strategyAry = rule.strategy.split(':');
            const errorMsg = rule.errorMsg;
            self.cache.push(() => {
                // 需要运用的目标策略
                const strategy = strategyAry.shift();
                strategyAry.unshift(dom.value);
                strategyAry.push(errorMsg);
                return strategies[strategy].apply(dom, strategyAry);
            });
        });
    }
    start () {
        for (let i = 0, validatorFunc; validatorFunc = this.cache[i++];){
            var errorMsg = validatorFunc();
            if (errorMsg) {
                return errorMsg;
            }
        }
    }
};
/***********************客户调用代码**************************/
const registerForm = document.getElementById( 'registerForm' );

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

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

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