iT邦幫忙

2024 iThome 鐵人賽

DAY 15
0
Software Development

全端實戰心法:小團隊的產品開發大小事系列 第 15

如何處理複雜的訂單狀態:Finite State Machine

  • 分享至 

  • xImage
  •  

在開發一個購物系統的時候,其訂單可能有多種狀態,如:待付款、付款完成、取消、過期、失敗,等等。

如果考慮這筆訂單又有貨運的狀態:待發貨、已出貨、配送中、退貨中,諸如此類多樣且複雜的狀態,應該如何管理怎麼好?

我們今天就從購物訂單,來聊聊全端開發常用到的「狀態 State」管理這件事!

狀態管理

在資料庫當中,購物的訂單可以簡單給一個狀態 state 的欄位來管理:

{ "id": 20240929001, "items": ["蘋果x1", "橘子x2"], "state": "待付款" }

每當狀態改變的時候,我們就直接修改這個欄位即可,例如買家已經付款成功了,便可以讀取此訂單並做修改 order.state = "付款完成"

然而,通常我們在改變訂單狀態之前,會做許多條件的判斷。像是要取消一筆訂單,一定要在買家付款之前才能做:

function updateOrderState(action, order) {
  if (action == "cancel") {
    if (order.state != "付款完成") {
      order.state = "取消";
    } else {
      throw new Error("已付款的訂單無法取消");
    }
  }
}

若是訂單的狀態不多,可能寫幾個條件尚可以把狀態的轉換給處理好,但是一旦狀態多了起來,就會讓條件判斷 if else 變得非常複雜,難以管理。

function updateOrderState(action, order) {
  if (action == "cancel") {
    if (order.state == "待付款" || order.state == "失敗") {
      order.state = "取消";
    } else if (order.state == "付款完成") {
      throw new Error("已付款的訂單無法取消");
    } else if (order.state == "過期") {
      order.state = "取消";
    }
  } else if (action == "pay") {
    if (order.state == "待付款") {
      order.state = "付款完成";
    } else if (order.state == "過期" || order.state == "取消" || order.state == "失敗") {
      throw new Error("無法對已過期、已取消或失敗的訂單進行付款");
    }
  }
  // ...
}

上面這段程式碼只實作部分的狀態轉換,並且只有「待付款」「付款完成」「取消」「過期」「失敗」這五種狀態,如果要開發更複雜的訂單管理系統,就算把這些 Code 寫得乾淨點,能拆小 Function 盡量拆,也會顯得難以管理,並且牽一髮而動全身。

這時候我們就需要 Finite State Machine 的幫助了。

有限狀態機 Finite State Machine

所謂 Finite State Machine,取首字母得到簡稱 FSM,中文翻譯為的有限狀態機,是一種表達狀態之間轉換的數學模型。

直接來看個訂單狀態的 FSM:

訂單狀態的 FSM 範例
*訂單狀態的 FSM 範例

我們可以看到圖中有幾種元素:圓圈表達「狀態」、箭頭上的文字表達「轉換」。最基本的 FSM 可以表達出狀態,以及從什麼狀態透過什麼轉換能到達另一個狀態。

對應到上面的 function updateOrderState(action, order),這邊的 action 就是可以對照一個「轉換」,而訂單的狀態則是我們事先定義清楚的。

例如在 JavaScript 中可以使用 javascript-state-machine 這個 Library,寫出以下的 FSM:

import StateMachine from 'javascript-state-machine';

const fsm = new StateMachine({
  init: '待付款',
  transitions: [
    { name: 'pay', from: '待付款', to: '付款完成' },
    { name: 'cancel', from: ['待付款', '過期', '失敗'], to: '取消' },
    { name: 'expire', from: '待付款', to: '過期' },
    { name: 'fail', from: '待付款', to: '失敗' },
    { name: 'recover', from: ['過期', '失敗'], to: '待付款' }
  ],
});

透過定義 Transitions,將每個 Transition 從什麼狀態(from)轉換到另一個狀態(to),就能把將我們前面寫的一堆 if else 簡化。

接著我們便能呼叫 transition 來改變現在的訂單狀態,例如當訂單為「待付款」時,呼叫 fsm.pay() 就能將 fsm.state 改變成「付款完成」;但是當訂單為「付款完成」時如果呼叫 fsm.cancel() 就無法轉變狀態,因為我們並沒有定義這個 Transition。

前端狀態管理

除了訂單這種商業邏輯上可以用 FSM,前端的許多元件也常常適用 FSM 的輔助。

例如我們要實作一個按鈕,可以對此 Hover、Click、Disable,此按鈕的狀態就可能會有 Init、Hovered、Clicked、Disabled。

按鈕的 FSM 範例
*按鈕的 FSM 範例

從原始的按鈕狀態,如果要點擊就能先 hover,然後再 click;但是每個狀態下都可能轉換成 Disabled,這樣稍嫌複雜的狀態轉換,同樣可以畫出 FSM 的轉換圖,並且依此實作。

只要是透過 FSM 的機制實作,如果我們覺得在 Init 的狀態下可以不被 hover 就直接 click,那也只要圖上的箭頭調整一下即可,但是用 if else 的方式來管理狀態,則會在要調整狀態的時候,難以 Debug 以及全局思考的情形。


上一篇
全端資結入門(二),Hash Table:為何 Hash Table 能達成 O(1) 的搜尋時間複雜度?
下一篇
全端資結入門(三),你用的 .sort() 是哪種排序法?
系列文
全端實戰心法:小團隊的產品開發大小事30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言