徹底消除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直達地址一線大廠面試題,高併發等主流技術資料

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