如何編寫易讀的條件分支結構

我不喜歡寫 switch-case 語句,雖然相比於 Java 來講,Javascript 的 switch-case 要更爲強大,但仍是沒法避免該結構固有的缺陷。一樣的狀況發生自 if-else 結構,當分支變得複雜以後,編寫出來的代碼在閱讀上簡直就是災難。html


條件分支語句的困境

首先須要明確對於複雜的條件分支語句的短板到底在哪裏:git

  • 閱讀困難
  • 不易擴展

對於程序員來講,上面的每一點都很致命。尤爲是當嵌套層次變深以後,在一個代碼塊的結尾處連續出現6、七個反大括號是一件很日常的事。程序員

這對於閱讀代碼的人來講簡直就是一個災難。github

尤爲是 switch-case 結構,不只有上面的問題,還有以下問題:設計模式

  • 不少人對 switch-case 結構的縮進有着不一樣的寫法,雖然不影響功能但也形成閱讀壓力;
  • 容易產生 case 語句‘穿越’的狀況(忘記寫或者故意不寫 break);
  • 每一個 case 語句不能獨享一個塊級做用域,在一個 case 語句中定義的變量不能在其餘 case 語句中重複定義;
  • 對於每一個 case 語句,若是有相同的操做,只能重複書寫。

對於前面三點來講比較容易理解,最後一條能夠經過以下例子進行說明。bash

好比在我寫的2048這個遊戲中,有一段業務邏輯是這樣:app

switch (direction) {
  case 'right':
    if (canMoveRight()) {
      moveRight();
    }
    break;
  case 'left':
    if (canMoveLeft()) {
      moveLeft();
    }
    break;
  case 'up':
    if (canMoveUp()) {
      moveUp();
    }
    break;
  case 'down':
    if (canMoveDown()) {
      moveDown();
    }
    break;
}
複製代碼

明顯能夠看出上面這段代碼是有問題的,對於上下左右四個方向來講,執行的動做徹底是同樣的:判斷是否能夠移動,若是能夠則移動。既然如此,咱們就應該使用一種更清晰的結構。後面咱們可使用對象字面量進行靜態配置。post

複雜的條件分支語句除了帶來閱讀上的困難以外,還會帶來不易擴展的問題。這一點很好理解,好比須要增長條件分支時,就不得不在原有的代碼塊上進行修改,這是不符合程序的「開放-封閉」原則的。後面咱們可使用職責鏈模式進行優化。優化


使用對象字面量進行靜態配置

仍是利用上面的2048遊戲的上下左右的移動邏輯,若是對 Javascript 有必定理解的程序員,不難寫出以下代碼:ui

const moveByDirectionMap = {
  'right': [canMoveRight, moveRight],
  'left': [canMoveLeft, moveLeft],
  'up': [canMoveUp, moveUp],
  'down': [canMoveDown, moveDown],
};
if (moveByDirectionMap[direction][0]()) {
  moveByDirectionMap[direction][1]();
}
複製代碼

相比於 switch-case 結構,上面的代碼易讀性提升了很多,同時也避免了書寫重複的操做。

在個人2048中,能夠說上面這種組織代碼的方式獲得了充分體現。


使用職責鏈模式

對於分支之間沒有優先級區分的狀況來講,使用對象字面量進行靜態配置的方法就已經足夠駕馭了。然而在實際場景中,每每還會遇到各個條件分支之間是有優先判斷順序的,典型的就是 if-else 結構。

好比以下場景:

if (salary > 500) {
  console.log('買耐克');
} else if (salary > 400) {
  console.log('買阿迪達斯');
} else if (salary > 300) {
  console.log('買李寧');
} else {
  console.log('買安踏');
}
複製代碼

固然現實中若是真是這麼簡單的場景就行了,上面這段代碼用來表示存在優先級順序的條件分支語句。接下來使用職責鏈模式進行優化。

首先創建 ChainNode 類:

class ChainNode {
  constructor(fn) {
    this.fn = fn;
    this.nextFn = null;
  }
  pass(...args) {
    const res = Reflect.apply(this.fn, this, args);
    return res === 'passNext' && this.nextFn
      ? Reflect.apply(this.nextFn.pass, this.nextFn, args)
      : res;
  }
}
複製代碼

而後將分支結構拆分:

const buyNike = salary => salary > 500 ? console.log('買耐克') : 'passNext';
const buyAdidas = salary => salary > 400 ? console.log('買阿迪達斯') : 'passNext';
const buyLining = salary => salary > 300 ? console.log('買李寧') : 'passNext';
const buyAnta = salary => console.log('買安踏');
複製代碼

最後,構建職責鏈:

// 封裝爲職責鏈結點
const buyNikeNode   = new ChainNode(buyNike);
const buyAdidasNode = new ChainNode(buyAdidas);
const buyLiningNode = new ChainNode(buyLining);
const buyAntaNode   = new ChainNode(buyAnta);
// 制定鏈條順序
buyNikeNode.nextFn   = buyAdidasNode;
buyAdidasNode.nextFn = buyLiningNode;
buyLiningNode.nextFn = buyAntaNode;
// 只需調用第一個結點
buyNikeNode.pass(100);
複製代碼

能夠看到輸出:

買安踏
複製代碼

以上就是職責鏈模式的基本思想,能夠看出,當須要新增長條件分支時,只須要插入一個職責鏈結點便可,相比於原來的 if-else 結構,避免了修改關鍵邏輯代碼的危險和麻煩。須要注意的是,切不可濫用職責鏈模式,必須是當條件分支結構達到必定的複雜度而且須要優先級判斷時,才能使用,不然只會起到副作用。


結語

寫出易讀易維護的代碼是每一個程序員的責任,這須要對程序語言自己和設計模式的的深刻理解。最後列出參考資料供感興趣的讀者繼續閱讀。

參考資料

相關文章
相關標籤/搜索