天天看點

淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

  先可以看下這篇部落格:如何用狀态機簡化代碼中複雜的 if else 邏輯 —— https://mp.weixin.qq.com/s/dDOA5JQQz3r4a7-yPl33Bg

一、狀态機的基本概念

  當處理的情況特别多,我們把每種情況的處理邏輯封裝成一個狀态,然後不同情況之間的轉換變成狀态的轉換。這種代碼組織形式就是狀态機。

  當每個狀态知道輸入某一段内容時轉到哪一個狀态,在一個循環内自動進行狀态的流轉和不同狀态的處理,這種叫做狀态自動機(automation),如果一個狀态在一種輸入下隻有一個後續狀态,這種就叫做确定性有限狀态自動機(DFA)。

二、typescript 源碼是怎麼利用狀态機使流程更清晰的

  首先 tsc 劃分了很多狀态,每種狀态處理一種邏輯。比如:

  • CreateProgram 把源碼 parse 成 ast
  • SyntaxDiagnostics 處理文法錯誤
  • SemanticDiagnostics 處理語義錯誤
  • Emit 生成目标代碼
淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

  typescript 就通過這種狀态的修改來完成不同處理邏輯的流轉,如果處理到結束狀态就代表流程結束。

淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

  這樣使得整體流程可以很輕易的擴充和修改,比如想擴充一個階段,隻要增加一個狀态,想修改某種狀态的處理邏輯,隻需要修改下狀态機的該狀态的轉向。而不是大量的 if else 混雜在一起,難以擴充和修改。可以看到,狀态機使得 ts 的編譯步驟可以靈活的擴充和修改。

三、詞法分析中的狀态機

  其實狀态機最常用的地方是用于詞法分析,因為每個 token 都是一種處理情況,自然會有很多 if else。

  更好的做法是使用狀态機(DFA)來做分詞,把每一種 token 的處理封裝成一個狀态。通過邊界條件的判斷來做狀态流轉,比如某個 wxml parser 分了這些狀态:

淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

  每種狀态處理一種情況的 token 的識别:

淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

四、業務代碼中的狀态機

  業務代碼中當遇到各種 if else 的判斷的時候同樣可以用狀态機來優化。把每種情況封裝成一個狀态,通過某一種條件觸發狀态的流轉,然後在狀态機裡面選擇不同的狀态處理邏輯進行處理。(其實下面總體來說還是用的 switch case結構)

淺析狀态機模式的了解以及如何使用狀态機模式簡化代碼裡複雜的 if else 邏輯

  總結一下:

1、我們首先明确了狀态機的概念:通過不同狀态封裝不同情況的處理邏輯,通過狀态的修改來完成處理邏輯之間的流轉。

2、如果每種狀态都知道下一個狀态是什麼,在一個循環内自動完成狀态流轉的狀态機,就是狀态自動機,當狀态為有限個時,就是有限狀态自動機(DFA)。

3、typescript compiler 就是通過狀态自動機來進行處理,封裝了很多個狀态,每個狀态知道下一個狀态是什麼,直到處理到終止狀态,就結束編譯。

4、詞法分析中一般會使用有限狀态自動機(DFA)來處理,不同 token 用不同的狀态來處理,通過輸入字元的不同來做狀态的流轉,處理完字元串就完成了分詞。

5、業務代碼中也經常會有不同情況做不同的處理,這些情況在一定的條件時會做轉換的場景,比如類似開始、暫停、結束、重新開始這種。這種代碼就很适合用狀态機來優化,不然會有很多的 if else。

  總之,當邏輯可以劃分為不同的情況,各種情況之間會互相轉換的時候就可以用狀态機來優化,能夠免去大量的 if else,并且代碼的可讀性、可擴充性、可維護性都會有一個很大的提升。

五、用狀态機模式思想消除代碼裡複雜的 if else 邏輯

  前一陣開發的一個 web 界面上有很多諸如“按鈕隐藏顯示”,“邊框隐藏顯示”,“伸縮” 等效果的切換,在展示不同内容的時候,這些配套的顯示控件需要跟着切換不同的狀态。迫于進度,使用的是 if..else, 或者 switch..case 的繁雜的 js 代碼來實作這些狀态的判斷和轉換。js 代碼很快到了 400~500行,變得很難了解。并且我要加入新的狀态切換的時候感覺比較困難。今天決心重構,于是忽然聯想起狀态機 (State Machine) 模式,不正好在這裡能用上嗎?而 js 中的對象表示文法正好非常友善構造“狀态表”和“狀态輪換表”,花了1個多小時,完成了這個工作。重構後代碼的邏輯可謂豁然開朗,帶來的僅僅是配置上的稍許備援,但是這個完全可以接受的。

//  狀态表定義
var statusTable  =  {
    '狀态1': {
        sizeCode:  1 ,
        headerUrl: ' / test1 / test2',
        bodyUrl: 'about:blank',
        showTitle:  true ,
        showBorder:  true ,
        showMin:  true ,
        showMax:  false ,
        showClose:  true
    },
    '狀态3': {
        sizeCode:  2 ,
        headerUrl: ' / test1 / test2',
        bodyUrl: ' / test1 / test2',
        showTitle:  true ,
        showBorder:  true ,
        showMin:  true ,
        showMax:  true ,
        showClose:  true
    },
    '狀态4': {
        sizeCode:  3 ,
        headerUrl: ' / test1 / test2',
        bodyUrl: ' / test1 / test2',
        showTitle:  true ,
        showBorder:  true ,
        showMin:  true ,
        showMax:  false ,
        showClose:  true
    },
    '狀态2': {
        sizeCode:  2 ,
        headerUrl: ' / test1 / test2',
        bodyUrl: ' / test1 / test2',
        showTitle:  true ,
        showBorder:  true ,
        showMin:  true ,
        showMax:  false ,
        showClose:  true
    }
};
//  目前狀态碼
var  currentStatusCode  =  '';

//  切換到狀态機某個狀态   
function  loadStatus(code){
     var  status  =  statusTable[code]  ||   null ;
     if ( ! status)  return ;
     //  update other status
     //  利用 status 做一系列設定。。如顯示隐藏按鈕等
    currentStatusCode  =  code;
}

//  示例函數 foo 和 bar.
//  一個函數是一套自定義的邏輯,定義一個狀态輪換表即可實作。
function  foo(){
     var  jumpTable  =  {
        '狀态1': '',
        '狀态2': '狀态1',
        '狀态3': '狀态4',
        '狀态4': ''
    };
    loadStatus(jumpTable[currentStatusCode]  ||  '');
}

function  bar(){
     var  jumpTable  =  {
        '狀态1': '狀态2',
        '狀态2': '',
        '狀态3': '狀态1',
        '狀态4': '狀态3'
    };
    loadStatus(jumpTable[currentStatusCode]  ||  '');
}      

  以上代碼來源于:

繼續閱讀