彻底消除if else, 让你的代码看起来更优雅 前言 最后

前言

应该有不少同学有遇到过充斥着if else的代码,面对这样的一团乱麻,简单粗暴地继续增量修改常常只会让复杂度越来越高,可读性越来越差。那么是时候重构了,花几分钟看看这篇文章, 说不定对你有一丢丢帮助。

场景一: 根据status显示对应名称

优化方案1:object对象

`const statusStr = {  
  '1': '待付款',  
  '2': '待发货',  
  '3': '已发货',  
  '4': '交易完成',  
  '5': '交易关闭',  
  'default': '',  
}  
  
const getStatus = (status) =>{  
  return statusStr[status] || statusStr['default']  
}  
`

将判断条件作为对象的属性名,将处理逻辑作为对象的属性值,在按钮点击的时候,通过对象属性查找的方式来进行逻辑判断.

优化方案2: Map对象

`const statusStr = new map([  
  ['1': ['待付款']],  
  ['2': ['待发货']],  
  ['3': ['已发货']],  
  ['4': ['交易完成']],  
  ['5': ['交易关闭']],  
  ['default': ['']],  
])  
  
const getStatus = (status) =>{  
  let actions = statusStr.get(status) || statusStr.get('default')  
  return  actions[0];  
}  
`

这样写用到了es6里的Map对象,那么Map对象和Object对象有什么区别呢?

一个对象通常都有自己的原型,所以一个对象总有一个"prototype"键。 一个对象的键只能是字符串或者Symbols,但一个Map的键可以是任意值。 你可以通过size属性很容易地得到一个Map的键值对个数,而对象的键值对个数只能手动确认。

场景二:多个condition对应名称

现在把问题升级一下, 以前按钮点击时候只需要判断status,现在还需要判断用户的身份:
「举个栗子:」

`const onButtonClick = (status,identity)=>{  
  if(identity == 'guest'){  
    if(status == 1){  
      //do sth  
    }else if(status == 2){  
      //do sth  
    }else if(status == 3){  
      //do sth  
    }else if(status == 4){  
      //do sth  
    }else if(status == 5){  
      //do sth  
    }else {  
      //do sth  
    }  
  }else if(identity == 'master') {  
    if(status == 1){  
      //do sth  
    }else if(status == 2){  
      //do sth  
    }else if(status == 3){  
      //do sth  
    }else if(status == 4){  
      //do sth  
    }else if(status == 5){  
      //do sth  
    }else {  
      //do sth  
    }  
  }  
}  
`

上面的例子我们可以看到,当你的逻辑升级为二元判断时,你的判断量会加倍,你的代码量也会加倍,这时怎么写更清爽呢?

优化方案1: 将condition用字符拼接形式存在Map对象里

``const actions = new Map([  
  ['guest_1', ()=>{/*do sth*/}],  
  ['guest_2', ()=>{/*do sth*/}],  
  ['guest_3', ()=>{/*do sth*/}],  
  ['guest_4', ()=>{/*do sth*/}],  
  ['guest_5', ()=>{/*do sth*/}],  
  ['master_1', ()=>{/*do sth*/}],  
  ['master_2', ()=>{/*do sth*/}],  
  ['master_3', ()=>{/*do sth*/}],  
  ['master_4', ()=>{/*do sth*/}],  
  ['master_5', ()=>{/*do sth*/}],  
  ['default', ()=>{/*do sth*/}],  
])  
  
const onButtonClick = (identity,status)=>{  
  let action = actions.get(`${identity}_${status}`) || actions.get('default')  
  action()  
}  
``

上述代码核心逻辑是:把两个条件拼接成字符串,并通过以条件拼接字符串作为键,以处理函数作为值的Map对象进行查找并执行,这种写法在多元条件判断时候尤其好用。

优化方案2: 将condition用字符拼接形式存在Object对象里

``const actions = {  
  'guest_1':()=>{/*do sth*/},  
  'guest_2':()=>{/*do sth*/},  
  //....  
}  
  
const onButtonClick = (identity,status)=>{  
  let action = actions[`${identity}_${status}`] || actions['default']  
  action()  
}  
``

优化方案3: 将condition用Object对象形式存在Map对象里

可能用查询条件拼成字符串有点别扭,那还有一种方案,就是用Map对象,以Object对象作为key:

`const actions = new Map([  
  [{identity:'guest',status:1},()=>{/*do sth*/}],  
  [{identity:'guest',status:2},()=>{/*do sth*/}],  
  //...  
])  
  
const onButtonClick = (identity,status)=>{  
  let action = [...actions].filter(([key,value])=>(key.identity === identity && key.status === status))  
  action.forEach(([key,value])=>value())  
}  
`

场景三:根据status做出相应操作

「举个栗子:」

`/**   
  orderType: 订单类型,1.500元定金用户,2.200元定金用户,3.普通用户  
  pay: 是否已支付 true/false (已下单但未支付用户需进入普通购买模式)  
  stock: 产品库存,已支付定金用户不受限  
*/  
let handleOrder = ( orderType, pay, stock ) => {  
  if ( orderType === 1 ){ // 500元定金购买模式  
      if ( pay === true ){ // 已支付定金  
          console.log( '500元定金预购, 得到100优惠券' );  
      }else{ // 未支付定金,降级到普通购买模式  
          if ( stock > 0 ){ // 用于普通购买的手机还有库存  
              console.log( '普通购买, 无优惠券' );  
          }else{  
              console.log( '手机库存不足' );  
          }  
      }  
  }  
  else if ( orderType === 2 ){ // 200元定金购买模式  
      if ( pay === true ){  
          console.log( '200元定金预购, 得到50优惠券' );  
      }else{  
          if ( stock > 0 ){  
              console.log( '普通购买, 无优惠券' );  
          }else{  
              console.log( '手机库存不足' );  
          }  
      }  
  }  
  else if ( orderType === 3 ){  
      if ( stock > 0 ){  
          console.log( '普通购买, 无优惠券' );  
      }else{  
          console.log( '手机库存不足' );  
      }  
  }  
};  
handleOrder( 1 , true, 500); // 输出: 500元定金预购, 得到100优惠券  
`

优化方案1: 职责链模式

`let order500 = ( orderType, pay, stock ) => {  
    if ( orderType === 1 && pay === true ){  
        console.log( '500元定金预购, 得到100优惠券' );  
    }else{  
        order200( orderType, pay, stock ); // 将请求传递给200元订单  
    }  
};  
let order200 = ( orderType, pay, stock ) => {  
    if ( orderType === 2 && pay === true ){  
        console.log( '200元定金预购, 得到50优惠券' );  
    }else{  
        orderNormal( orderType, pay, stock ); // 将请求传递给普通订单  
    }  
};  
let orderNormal = ( orderType, pay, stock ) => {  
    if ( stock > 0 ){  
        console.log( '普通购买, 无优惠券' );  
    }else{  
        console.log( '手机库存不足' );  
    }  
};  
order500( 1 , true, 500); // 输出:500 元定金预购, 得到100 优惠券  
`

tips:职责链模式的定义是使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

优化方案2: 函数式编程

`import R from 'ramda'  
  
var fn = R.cond([  
  [R.equals(0),   R.always('water freezes at 0°C')],  
  [R.equals(100), R.always('water boils at 100°C')],  
  [R.T,           temp => 'nothing special happens at ' + temp + '°C']  
]);  
  
fn(0); //=> 'water freezes at 0°C'  
fn(50); //=> 'nothing special happens at 50°C'  
fn(100); //=> 'water boils at 100°C'  
`

利用ramda等函数式编程库解决这种问题

场景四: 根据范围去进行不同处理

「举个栗子:」 比如某平台的信用分数评级,超过700-950,就是信用极好,650-700信用优秀,600-650信用良好,550-600信用中等,350-550信用较差。

`function showGrace(grace) {  
    let _level='';  
    if(grace>=700){  
        _level='信用极好'  
    }  
    else if(grace>=650){  
        _level='信用优秀'  
    }  
    else if(grace>=600){  
        _level='信用良好'  
    }  
    else if(grace>=550){  
        _level='信用中等'  
    }  
    else{  
        _level='信用较差'  
    }  
    return _level;  
}  
`

优化方案1: 用look-up表,把配置数据和业务逻辑分离

`function showGrace(grace,level,levelForGrace) {  
    for(let i=0;i<level.length;i++){  
        if(grace>=level[i]){  
            return levelForGrace[i];  
        }  
    }  
    //如果不存在,那么就是分数很低,返回最后一个  
    return levelForGrace[levelForGrace.length-1];  
}  
let graceForLevel=[700,650,600,550];  
let levelText=['信用极好','信用优秀','信用良好','信用中等','信用较差'];  
`

使用配置数据和业务逻辑分离的形式,好处:
(1) 修改配置数据比业务逻辑修改成本更小,风险更低
(2) 配置数据来源和修改都可以很灵活
(3) 配置和业务逻辑分离,可以更快的找到需要修改的代码
(4) 配置数据和业务逻辑可以让代码风格统一

小结

很多情况下我们都可以使用更灵活的方式去替代if else以及switch, 但也不是所有的if else都需要替代, 视情况而定。

最后

傻姑粉丝福利:更多一线大厂面试题,高并发等主流技术资料尽在下方
github直达地址一线大厂面试题,高并发等主流技术资料

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章