简化分支判断的设计模式

Posted 线流五里牌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简化分支判断的设计模式相关的知识,希望对你有一定的参考价值。

  很多时候会发现自己在写代码的时候写了一坨if else 语句使得自己的代码看起来很丑,随着业务量的增大,代码变得很难维护,之前想到能替换if else的只有switch,其实效果并没有明显的提升,现在在看设计模式方面的知识,发现两种设计模式能够解决分支判断的臃肿问题。

状态模式

使用场景

  大家都知道超级玛丽的游戏吧,玛丽要吃蘑菇,他就要挑起,顶出墙壁里的蘑菇;玛丽想到悬崖的另一边,他就要跳起;玛丽想躲避被前面的乌龟咬到,他就要开枪打死乌龟;前面飞过炮弹,玛丽就要蹲下躲避;时间不够了,就要加速奔跑···

  如果这个游戏要用if或者switch条件判断,显得有些疲惫,如果使用状态模式将‘跳跃’、‘开枪’、‘蹲下’和‘奔跑’作为一个个的状态来开发,之后在遇到不同情况的时候直接使用状态,思路将变得清晰。

代码实现

  首先创建一个状态对象,内部保存状态变量,然后内部封装好每种动作对应的状态,最后状态对象返回一个接口对象,它可以对内部的状态修改或者调用。

代码如下:

/*
* 超级玛丽里面的状态: 跳跃、开枪、蹲下、奔跑等
 */
const MarryState = function() {
    // 内部状态私有变量
    let _currentState = {};
    // 动作与状态方法映射
    const states = {
        jump() {
            // 跳跃
            console.log(‘jump‘);
        },
        move() {
            // 移动
            console.log(‘move‘);
        },
        shoot() {
            // 射击
            console.log(‘shoot‘);
        },
        squat() {
            // 蹲下
            console.log(‘squat‘);
        }
    };
    // 动作控制类
    const Action = {
        // 改变状态方法
        changeState() {
            // 组合动作通过传递多个参数实现
            let arg = arguments;
            // 重置内部状态
            _currentState = {};
            if(arg.length) {
                // 遍历动作
                for(let i = 0,len = arg.length; i < len; i++) {
                    // 向内部状态中添加动作
                    _currentState[arg[i]] = true;
                }
            }
            // 返回动作控制类
            return this;
        },
        // 执行动作
        goes() {
            console.log(‘触发一次动作‘);
            // 遍历内部状态保存的动作
            for(let i in _currentState) {
                // 如果改动作存在则执行
                states[i] && states[i]();
            }
            return this;
        }
    };
    // 返回接口方法 change、goes
    return {
        change: Action.changeState,
        goes: Action.goes
    }
};

 

使用方式:

// 创建一个超级玛丽
const marry = new MarryState();
marry
    .change(‘jump‘,‘shoot‘)                 // 添加跳跃与射击动作
    .goes()                                 // 执行动作
    .goes()                                 // 执行动作
    .change(‘shoot‘)                        // 添加射击动作
    .goes();                                // 执行动作

 

  可以发现,状态模式中的状态可以连续使用。

原理和优点

  原理:将条件判断的不同结果转化为对象的内部状态,作为对象内部的私有变量。

  好处:当我们需要增加、修改、调用、删除某种状态方法时就会很容易,也方便了我们对状态对象中内部状态的管理。

  用途:状态模式是为了解决程序中臃肿的分支判断语句问题,将每个分支转化为一种状态独立出来,方便每种状态的管理又不至于每次执行时遍历所有分支。

  总之,状态模式的最终目的是简化分支判断流程。

策略模式

使用场景

  商品的促销活动。在圣诞节,一部分商品5折出售,一部分商品8折出售,一部分商品9折出售,等到元旦,普通用户满100减30,高级VIP用户满100减50···

  如果这种情况用if或switch来写将是一件很费时费力的事情。

  而且对于圣诞节或者元宵节,当天的一种商品只有一种促销策略,根本不用关心其他的促销状态。

代码实现

  首先要将这些促销算法封装在一个策略对象内,然后对每种商品的策略调用时,直接对策略对象中的算法调用即可,为方便我们的管理与使用,我们需要返回一个调用接口对象来实现对策略算法对调用。

代码如下:

/*
 * 价格策略对象
 */
const PriceStrategy = function () {
    // 内部算法对象
    const strategy = {
        // 100 返 30
        return30(price) {
            // parseInt可通过~~、|等运算符替换,要注意此时price要在[-2147483648,2147483647]之间
            // +price 转换为数字类型
            return +price + parseInt(price / 100) * 30;
        },
        // 100 返 50
        return50(price) {
            return +price + parseInt(price / 100) * 50;
        },
        // 9 折
        percent90(price) {
            // javascript 在处理小数乘除法有bug,故运算前转化为整数
            return price * 100 * 90 / 10000;
        },
        // 8 折
        percent80(price) {
            // JavaScript 在处理小数乘除法有bug,故运算前转化为整数
            return price * 100 * 80 / 10000;
        },
        // 5 折
        percent50(price) {
            // JavaScript 在处理小数乘除法有bug,故运算前转化为整数
            return price * 100 * 50 / 10000;
        }
    };
    // 策略算法调用接口
    return function (algorithm, price) {
        // 如果算法存在,则调用算法,否则返回false
        return stragtegy[algorithm] && stragtegy[algorithm](price);
    }
}();

 

使用方式:

const price = PriceStrategy(‘return50‘, ‘343.20‘);
console.log(price);

表单验证中的策略模式

代码如下:

/*
* 表单正则验证侧罗对象
 */
const InputStrategy = function () {
    const strategy = {
        // 是否为空
        notNull(value) {
            return /\s+/.test(value)
        },
        // 是否是一个数字
        number(value) {
            return /^[0-9]+(\.[0-9]+)?$/.test(value);
        },
        // 是否为本地电话
        phone(value) {
            // 例:010-94837837  或  0310-8899766
            return /^\d{3}\-\d{8}$|^\d{4}\-\d{7}$/.test(value);
        }
    };
    return {
        // 验证接口 type 算法 value 表单值
        check(type, value) {
            // 去除首尾空白符
            value = value.replace(/^\s+|\s+$/g, ‘‘);
            return strategy[type] ? strategy[type](value) : ‘没有该类型等检测方法‘;
        },
        // 添加策略
        addStrategy(type, fn) {
            strategy[type] = fn;
        }
    }
};

  可以发现,表单验证返回的接口中添加了一个添加策略接口。因为已有的策略即使再多,有时候也不能满足其他工程师的需求,这样就可以增强策略对象的拓展性。

// 拓展  可以延伸算法
InputStrategy.addStrategy(‘nickname‘, function (value) {
    return /^[a-zA-Z]\w{3,7}$/.test(value);
});

 

原理和优点

  策略模式:将定义的一组算法封装起来,使其相互之间可以替换。

  策略模式不需要管理状态、状态间没有依赖关系、策略之间可以相互替换、在策略对象内部保存的是相互独立的一些算法。

  策略模式使得算法脱离与模块逻辑而独立管理,使我们可以专心研发算法,而不必受模块逻辑所约束。

其他

  设计模式并不是很高深不可理解的一门学问,只是根据经验把复杂的业务逻辑整理清楚,使之更容易操作化。

  根据不同的业务逻辑选择不同的设计模式有助于简化代码,也有助于代码的解耦,使得代码更加有效和可维护。

  参考书籍:《JavaScript设计模式》

 

以上是关于简化分支判断的设计模式的主要内容,如果未能解决你的问题,请参考以下文章

12状态模式

流程控制

IT兄弟连 Java语法教程 流程控制语句 分支结构语句2

Go语言基础之流程控制

Go语言基础之流程控制

GitGit 分支管理 ( 克隆远程分支 | 克隆 master 分支 git clone | 查看远程分支 git branch -a | 克隆远程分支 git checkout -b )(代码片段