探索FSM (有限狀態(tài)機)應用

有限狀態(tài)機(FSM) 是計算機科學中的一種數(shù)學模型,可用于表示和控制系統(tǒng)的行為夏块。它由一組狀態(tài)以及定義在這些狀態(tài)上的轉換函數(shù)組成北专。FSM 被廣泛用于計算機程序中的狀態(tài)機制。

有限狀態(tài)機(FSM)應用場景

  • 在各種自動化系統(tǒng)的應用: 例如交通信號燈、地鐵站的旋轉閘門、銀行自動取款機等违孝。通過對狀態(tài)和轉換函數(shù)的定義膏燕,可以實現(xiàn)對系統(tǒng)行為的精確控制。

    交通信號燈狀態(tài)流轉圖

地鐵站的旋轉閘門狀態(tài)流轉圖

銀行自動取款機狀態(tài)流轉圖

  • 在編程領域的應用: 例如在編寫編譯器和解釋器時,可以使用有限狀態(tài)機(FSM) 來處理詞法分析。例如:JSON.Parse

  • 在Notion中應用: 可以使用 有限狀態(tài)機(FSM) 的相關概念來構建各種工作流程,例如狀態(tài)轉換圖睡榆、狀態(tài)轉換表等。

  • 在web中應用: 我們熟悉的 Promise 也是一個狀態(tài)機袍榆,具有三個狀態(tài):pending胀屿、resolved。rejected蜡塌。

    Promise狀態(tài)流轉圖


登錄功能流轉圖

類似這樣的狀態(tài)機的例子數(shù)不勝數(shù)碉纳,甚至于,人也是一種極其復雜的狀態(tài)機馏艾,給定一種刺激或多種刺激組合劳曹,也會觸發(fā)人從某種狀態(tài)過渡到另一種狀態(tài)奴愉。只不過復雜程度極高,以至于現(xiàn)代科學完全無法解密這種狀態(tài)機铁孵。

有限狀態(tài)機(FSM)實現(xiàn)原理

具體來說锭硼,F(xiàn)SM由以下幾部分組成:

  • 初始狀態(tài):系統(tǒng)的初始狀態(tài)。
  • 狀態(tài)集合:表示系統(tǒng)可能處于的各種狀態(tài)蜕劝。
  • 轉移函數(shù):定義系統(tǒng)在不同狀態(tài)之間的轉移條件和結果檀头。
  • 終止狀態(tài):系統(tǒng)在某個狀態(tài)下可以停止計算。

有限狀態(tài)機(FSM) 的實現(xiàn)基于狀態(tài)轉移圖岖沛。狀態(tài)轉移圖 是一個有向圖暑始,它表示有限狀態(tài)機(FSM) 中狀態(tài)之間的轉移關系。在狀態(tài)轉移圖中婴削,每個狀態(tài)表示系統(tǒng)的某種狀態(tài)廊镜,每個轉移表示系統(tǒng)從一個狀態(tài)轉移到另一個狀態(tài)的條件和結果。

實現(xiàn)簡易的有限狀態(tài)機(FSM)

實現(xiàn)步驟

  • 當狀態(tài)機開始執(zhí)行時唉俗,它會自動進入初始化狀態(tài)(initial state)嗤朴。
  • 每個狀態(tài)都可以定義,在進入(onEnter)或退出(onExit)該狀態(tài)時發(fā)生的行為事件(actions)虫溜,通常這些行為事件會攜帶副作用(side effect)雹姊。
  • 每個狀態(tài)都可以定義觸發(fā)轉換(transition)的事件。
  • 轉換定義了在退出一個狀態(tài)并進入另一個狀態(tài)時衡楞,狀態(tài)機該如何處理這種事件吱雏。
  • 在狀態(tài)轉換發(fā)生時,可以定義可以觸發(fā)的行為事件寺酪,從而一般用來表達其副作用坎背。

狀態(tài)轉移圖

function createMachine(stateMachineDefinition) {
  const machine = {
    value: stateMachineDefinition.initialState,
    performTransition(currentState, event) {
      const currentStateDefinition = stateMachineDefinition[currentState];
      const destinationTransition = currentStateDefinition.transitions[event];
      if (!destinationTransition) {
        return;
      }
      const destinationState = destinationTransition.target;
      const destinationStateDefinition =
        stateMachineDefinition[destinationState];

      destinationTransition.action();
      currentStateDefinition.actions.onExit();
      destinationStateDefinition.actions.onEnter();

      machine.value = destinationState;

      return machine.value;
    },
  };
  return machine;
}

const machine = createMachine({
  initialState: "off",
  off: {
    actions: {
      onEnter() {
        console.log("off: onEnter");
      },
      onExit() {
        console.log("off: onExit");
      },
    },
    transitions: {
      switch: {
        target: "on",
        action() {
          console.log('transition action for "switch" in "off" state');
        },
      },
    },
  },
  on: {
    actions: {
      onEnter() {
        console.log("on: onEnter");
      },
      onExit() {
        console.log("on: onExit");
      },
    },
    transitions: {
      switch: {
        target: "off",
        action() {
          console.log('transition action for "switch" in "on" state');
        },
      },
    },
  },
});

let state = machine.value;
console.log(`current state: ${state}`);
state = machine.performTransition(state, "switch");
console.log(`current state: ${state}`);
state = machine.performTransition(state, "switch");
console.log(`current state: ${state}`);

有限狀態(tài)機(FSM)的 應用實現(xiàn)

在狀態(tài)比較多的情況下,把狀態(tài)寄雀、事件及 transitions 集中到一個狀態(tài)機中,進行統(tǒng)一管理陨献。這樣不需要寫太多的 if-else盒犹,或者 case 判斷,如果增加狀態(tài)和事件眨业,也便于代碼的維護和擴展急膀。

文本解析器

實現(xiàn)思路

  • 確定狀態(tài)和輸入
    在編寫 FSM 之前,我們需要確定我們的狀態(tài)和輸入龄捡。在這個例子中卓嫂,我們將定義三個狀態(tài):起始狀態(tài)、數(shù)字狀態(tài)和字符串狀態(tài)聘殖。我們還將定義四個輸入:數(shù)字晨雳、字母行瑞、引號和空格。
  • 定義狀態(tài)機類
    現(xiàn)在餐禁,我們可以編寫代碼來實現(xiàn)我們的 FSM 血久。我們需要定義一個狀態(tài)機類,它將接受輸入帮非,并根據(jù)轉移規(guī)則轉換狀態(tài)氧吐。該類應該包含以下屬性:
    • currentState:當前狀態(tài)。
    • states:狀態(tài)列表末盔。
    • transitions:轉移列表筑舅。
      它還應該包含以下方法:
    • transition:該方法接受一個輸入參數(shù) input,根據(jù)當前狀態(tài)以及輸入參數(shù)陨舱,執(zhí)行相應的狀態(tài)轉換翠拣。
  • 定義轉移規(guī)則
    我們還需要定義狀態(tài)之間的轉移規(guī)則。為此隅忿,我們將使用轉移列表心剥,其中包含狀態(tài)之間的映射和輸入。轉移規(guī)則應該考慮當前狀態(tài)和輸入背桐,并根據(jù)它們確定下一個狀態(tài)优烧。如果當前狀態(tài)和輸入沒有匹配的轉移規(guī)則,則應該拋出一個異常链峭。
  • 解析文本
    現(xiàn)在畦娄,我們可以使用狀態(tài)機解析文本。我們需要將文本拆分為單詞弊仪,并將每個單詞作為輸入提供給狀態(tài)機熙卡。在處理完所有輸入后,我們可以通過調用 getInputType 方法來獲取解析的令牌励饵。

示例代碼

const STATES = {
  START: "start",
  NUMBER: "number",
  STRING: "string",
};

const INPUTS = {
  NUMBER: "number",
  LETTER: "letter",
  SPACE: "space",
  QUOTE: "quote",
};

const TRANSITIONS = [
  {
    currentState: STATES.START,
    input: INPUTS.NUMBER,
    nextState: STATES.NUMBER,
  },
  {
    currentState: STATES.START,
    input: INPUTS.LETTER,
    nextState: STATES.STRING,
  },
  { currentState: STATES.START, input: INPUTS.SPACE, nextState: STATES.START },
  { currentState: STATES.START, input: INPUTS.QUOTE, nextState: STATES.STRING },
  {
    currentState: STATES.NUMBER,
    input: INPUTS.NUMBER,
    nextState: STATES.NUMBER,
  },
  { currentState: STATES.NUMBER, input: INPUTS.SPACE, nextState: STATES.START },
  {
    currentState: STATES.STRING,
    input: INPUTS.LETTER,
    nextState: STATES.STRING,
  },
  { currentState: STATES.STRING, input: INPUTS.SPACE, nextState: STATES.START },
  { currentState: STATES.STRING, input: INPUTS.QUOTE, nextState: STATES.START },
];

class TextParse {
  constructor() {
    this.currentState = STATES.START;
    this.buffer = "";
    this.type;
  }

  performTransition(input) {
    const transition = TRANSITIONS.find(
      (t) => t.currentState === this.currentState && t.input === input.type
    );
    if (!transition)
      throw new Error(
        `Invalid input "${input.value}" for state "${this.currentState}"`
      );

    this.currentState = transition.nextState;

    if (this.currentState === STATES.START) {
      const token = this.buffer;
      const type = this.type;
      this.buffer = "";
      this.type = "";
      return {
        type,
        value: token,
      };
    } else {
      this.buffer += input.value;
      this.type = input.type;
    }
  }
}

function textParse(input) {
  const textParse = new TextParse();
  const tokens = [];

  for (let i = 0; i < input.length; i++) {
    const char = input[i];

    try {
      const token = textParse.performTransition({
        type: getInputType(char),
        value: char,
      });

      if (token) {
        tokens.push(token);
      }
    } catch (e) {
      console.error(e.message);
      return null;
    }
  }

    const lastToken = textParse.performTransition({ type: INPUTS.SPACE });

  if (lastToken) {
    tokens.push(lastToken);
  }

  return tokens;
}

function getInputType(char) {
  if (/[0-9]/.test(char)) {
    return INPUTS.NUMBER;
  } else if (/[a-zA-Z]/.test(char)) {
    return INPUTS.LETTER;
  } else if (/[\s\n\t\r]/.test(char)) {
    return INPUTS.SPACE;
  } else if (char === '"') {
    return INPUTS.QUOTE;
  } else {
    throw new Error(`Unknown input type for "${char}"`);
  }
}

// Example usage:
console.log(textParse('123 abc "def ghi" 456')); 
// [
//   { type: 'number', value: '123' },
//   { type: 'letter', value: 'abc' },
//   { type: 'letter', value: '"def' },
//   { type: 'letter', value: 'ghi' },
//   { type: '', value: '' },
//   { type: 'number', value: '456' }
// ]

示例代碼

web 應用

使用 有限狀態(tài)機(FSM) 結合 React 構建 web 應用驳癌,不局限于身份認證,登錄役听,步驟表單颓鲜,有蠻多 web 應用在
有限狀態(tài)機(FSM)的實踐 ,下面主要描述 從有限狀態(tài)機(FSM)在服務端拉取數(shù)據(jù)的狀態(tài)轉移上的應用

  • 狀態(tài)轉移圖
  • 狀態(tài)集(States), 轉換規(guī)則(Transitions)
const states = {
  INITIAL: "idle",
  LOADING: "loading",
  SUCCESS: "success",
  FAILURE: "failure",
};
const transitions = {
  [states.INITIAL]: {
    fetch: () => /* Returns states.LOADING */,
  },

  [states.LOADING]: {},

  [states.SUCCESS]: {
    reload: () => /* Returns states.LOADING */,
    clear: () => /* Returns states.INITIAL */,
  },

  [states.FAILURE]: {
    retry: () => /* Returns states.LOADING */,
    clear: () => /* Returns states.INITIAL */,
  },
}

示例代碼

總結

結合前端應用的探索體現(xiàn)的不多典予,可以再作為第二篇內容去探討甜滨,有興趣的同學可以嘗試一下 有限狀態(tài)機(FSM) 在 web 上的應用探索,以及 Xstate庫(FSM封裝的功能性庫) 的應用瘤袖,以及跟 狀態(tài)管理庫 差異化的知識衣摩。在這里提醒一點,狀態(tài)管理庫 (Redux)Xstate 并不是互斥的捂敌,Xstate 關注的是如何設計狀態(tài)艾扮,狀態(tài)管理庫關注的是如何管理狀態(tài)既琴。事實上,狀態(tài)機幾乎可以與任何無主見的狀態(tài)管理工具一起使用栏渺。我鼓勵您探索各種方法呛梆,以確定最適合您、您的團隊和您的應用程序的方法磕诊。

參考資料

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末填物,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子霎终,更是在濱河造成了極大的恐慌滞磺,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莱褒,死亡現(xiàn)場離奇詭異击困,居然都是意外死亡,警方通過查閱死者的電腦和手機广凸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進店門阅茶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人谅海,你說我怎么就攤上這事脸哀。” “怎么了扭吁?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵撞蜂,是天一觀的道長。 經常有香客問我侥袜,道長蝌诡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任枫吧,我火速辦了婚禮浦旱,結果婚禮上,老公的妹妹穿的比我還像新娘九杂。我一直安慰自己闽寡,他們只是感情好,可當我...
    茶點故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布尼酿。 她就那樣靜靜地躺著,像睡著了一般植影。 火紅的嫁衣襯著肌膚如雪裳擎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天思币,我揣著相機與錄音鹿响,去河邊找鬼羡微。 笑死,一個胖子當著我的面吹牛惶我,可吹牛的內容都是我干的妈倔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绸贡,長吁一口氣:“原來是場噩夢啊……” “哼盯蝴!你這毒婦竟也來了?” 一聲冷哼從身側響起听怕,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤捧挺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后尿瞭,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闽烙,經...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年声搁,在試婚紗的時候發(fā)現(xiàn)自己被綠了黑竞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 37,997評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡疏旨,死狀恐怖很魂,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情充石,我是刑警寧澤莫换,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站骤铃,受9級特大地震影響拉岁,放射性物質發(fā)生泄漏。R本人自食惡果不足惜惰爬,卻給世界環(huán)境...
    茶點故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一喊暖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧撕瞧,春花似錦陵叽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至页畦,卻和暖如春胖替,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工独令, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留端朵,地道東北人。 一個月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓燃箭,卻偏偏與公主長得像冲呢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子招狸,可洞房花燭夜當晚...
    茶點故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內容