State Pattern 介紹及 Ruby 實作

Whyayen
嗨,世界
Published in
Nov 12, 2020
Photo by Brendan Church on Unsplash

一件事物可能有不同的狀態或階段,而不同的狀態也會使行為有所不同,例如:線上購物,一項交易從未付款、已付款未出貨、出貨、到貨等有著不同的狀態,不同的狀態也會有不同的行為,當未付款時提供客戶付款方式,當付款後告知廠商出貨,同時狀態間的變換也會有一些條件限制,像是未付款的交易不可能直接進到出貨狀態,而實作這樣的案例最糟糕的作法便是透過一堆 if/else 去做判斷,這會導致狀態難以維護及新增,且難理解狀態間的變換邏輯,而 State Pattern 主要概念則可以避免這些常見的問題。

概念

State Pattern 主要概念是當它的內部狀態變更時,讓物件改變它的行為。State Pattern 的概念與有限狀態機很像,且 State Pattern 結構跟 Strategy Pattern 很像,不過 State Pattern 與 Strategy Pattern 有一些差異,我們稍後再提。

State Pattern 主要被設定來解決兩個問題:

  • 物件的行為應該要改變,當物件內部的狀態改變
  • 狀態的行為應該被獨立定義,當新增新狀態時,不應該影響現有狀態

直接在 Class 裡面實作狀態定義會導致在新增狀態、改變原有狀態上相當困難,因為 Class 把狀態跟行為綁死了,無法透過不修改 Class 的前提下去新增、修改原有狀態,要解決上述的問題,可以透過下列兩個方法:

  • 定義一個 state interface 及方法,不同的 state 皆是一個新的 Class 透過 state interface 實作這個 state 的行為
  • Class 透過這些 state 物件去委派 (delegate) 狀態的行為,取代直接在 Class 裡面實作狀態的行為

結構

State Design Pattern UML Class Diagram — Wikipedia

不直接在 Context 類別定義狀態的行為,而是定義 State 共同的介面,在將不同的狀態基於這個介面進行實作狀態各自的行為,並透過 Context 委派各個狀態的行為,在初始 Context 時建立 State A 物件,當進行呼叫 state.operation 時,會優先呼叫 State A 定義的行為,並透過 setState 將狀態變成 State B,在呼叫 state.operation 時便會呼叫 State B 定義好的行為,並將狀態變為 State A。

實作

基於上述結構,我們透過 Ruby 建立一個像是購物車結帳程序吧!首先我們有三個狀態,分別是 To Pay(待付款)、To Ship(已付款,待出貨)、To Receive(待收貨),顯示不同的通知,這裡不探討狀態轉變的條件(例如:檢查是否已付款,付款才從 To Pay 跳到 To Ship)。

  • To Pay(待付款):顯示給家付款通知
  • To Ship(待出貨):顯示給家出貨通知
  • To Receive(待收貨):顯示給家取貨通知

先建立一個 State 的 interface

接著實作 3 個 States,首先是 To Pay 這個狀態,當我們 print 出給買家的通知後,我們便把狀態變成 To Ship

ToShip 則是顯示賣家的通知,之後便把狀態變成 ToReceive

上面四個檔案我們已經完成了 State 的實作,接著我們只要建立 Context 便可模擬從 To Pay 到 To Receive ,不同的狀態顯示不同的通知了!

在 Context 建立時,我們先預設狀態在 To Pay(待付款),接著我們提供一個 set_state 方法,供 State 內可以直接呼叫 context.state 切換狀態,最後 notification 方法則是透過呼叫 context.notification,我們便可顯示不同狀態的通知了。

由於狀態切換是在各個 State 的實作內,因此我們呼叫 context.notification 每次輸出的都是不同狀態的通知,所以 State Pattern 其實很簡單,至於狀態間的切換並無明確定義需要在哪一塊進行撰寫,大家可以依照自己的需求去做狀態間的切換。

State Pattern 與 Strategy Pattern 的比較

先從使用場景來說,State Pattern 基本上是一個物件擁有多種狀態,可以從 A > B > C,不同狀態有不同的行為,因此像一筆訂單會在不同時間點有不同狀態,使用 State Pattern 較為合適。而如果像車子在出廠時,便會決定你的煞車系統是普通煞車,還是 ABS 煞車,這中間並不會換來換去,原則上你是普通煞車,就會一直是普通煞車,除非你想透過改裝,變成 ABS 煞車,那麼煞車系統的變換(Strategy 的變換),就你自己(Client)決定。

  • Strategy 用在你需要依照不同情境有不同的處理邏輯,而使用者需要負責提供相對應的邏輯(strategy)
  • State Pattern 下,Class 本身基於不同狀態(State),而有不同的行為

兩個 Pattern 雖極為相似,且使用相同的結構,但所要解決的問題不同,因此在採用時,也應思考場景本身,使用什麼 Pattern 並沒絕對的對錯,但不要為了用而用。

參考資料

--

--