前言: 狀態者模式
優勢在可將複雜的物件狀態條件,以物件方式來減少條件式的判斷程式
可由物件自身的狀態,決定之後的動作行為.
狀態者模式 說明: 需求簡易流程如下
這是一個簡單的訂單流程圖
我們可看到從建立訂單開始->最後判斷成功或取消訂單 看似簡單但需要寫一定程度的判斷條件式,而且也要做一定程度的逆向流程防呆.
這裡先貼上 未使用狀態者模式的程式碼: PaymentContext.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 public class PaymentContext { Product _Item { get ; set ; } PayStatus _status { get ; set ; } public PaymentContext (Product p ) { _Item = p; _status = PayStatus.Init; } public string SetStatus (PayStatus status ) { string result = $"修改成功{status.ToString()} " ; switch (_status) { case PayStatus.Init: if (status == PayStatus.Init) result = "請勿重新建立訂單" ; else _status = status; break ; case PayStatus.Success: result = "訂單成功請勿修改" ; break ; case PayStatus.Cancel: result = "訂單取消請勿修改" ; break ; case PayStatus.Processing: if (status == PayStatus.Init) result = "請勿重新建立訂單" ; else _status = status; break ; } return result; } public string RunProcess () { switch (_status) { case PayStatus.Init: _status = PayStatus.Processing; return "交易建立中..." ; case PayStatus.Success: return "交易完成" ; case PayStatus.Cancel: return "交易取消完成" ; case PayStatus.Processing: if (_Item.Price > 300 ) { _status = PayStatus.Cancel; return "物件超過300元 交易取消中" ; } _status = PayStatus.Success; return "交易中請稍後" ; } return "不在狀態內" ; } }
裡面有SetStatus
和 RunProcess
方法
RunProcess 方法 就是將商品一個往下一個流程推進
SetStatus 方法 可以改變商品狀態
上面類別中的程式碼 目前有點小複雜但還算簡單,但等日後需求越來越多 後人一直把程式碼寫入Switch case
或if ... else
中就會導致程式碼越來越複雜
這個情境我們可以嘗試使用 State Pattern(狀態者模式)
幫助我們將每個自身狀態封裝到物件裡面,由每個狀態來決定後面動作
我們可發現 每個流程都可以使用 RunningProcee
和 SetSatus
這兩個動作
就可開出一個抽象類別,裡面有這兩個抽象方法,給之後的狀態子類去實現.
1 2 3 4 5 6 7 public abstract class PaymentSatusBase { protected PaymentGate _gate; public abstract string Running (Product p ) ; public abstract string SetSatus (PayStatus s ) ; }
PaymentGate
是給外部呼叫端使用的類別,我們可比較上面之前PaymentContext
類別可看到if....else
全部不見了,
因為狀態封裝到各個類別中了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class PaymentGate { Product _product; internal PaymentSatusBase CurrnetProceess { get ; set ; } public PaymentGate (Product p ) { _product = p; CurrnetProceess = new InitSatus(this ); } internal PayStatus CurrnetStatus { get ; set ; } public string SetStatus (PayStatus status ) { return CurrnetProceess.SetSatus(status); } public string RunProcess () { return CurrnetProceess.Running(_product); } }
如何新建一個流程物件?
首先我們需要先取得當前使用者使用的 PaymentGate
引用並傳入建構子當作參數
實現Running和SetStatus方法,並將此狀態的邏輯寫上
執行完後需要更改下個流程,可以將值賦予給CurrnetProceess
屬性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 public class ProcessSatus : PaymentSatusBase { public ProcessSatus (PaymentGate g ) { _gate = g; } public override string Running (Product p ) { string result = "交易中請稍後" ; if (p.Price > 300 ) { result = "物件超過300元 交易取消中" ; _gate.CurrnetProceess = new CancelSatus(_gate); } else _gate.CurrnetProceess = new SuccessSatus(_gate); return result; } public override string SetSatus (PayStatus s ) { string result = string .Empty; if (s == PayStatus.Init) result = "請勿重新建立訂單" ; return result; } }
說明: 以流程進行中為例子.
他會判斷商品使用超過300元,來決定下個流程 所以我們就把這個邏輯寫在此類中.
另外後面幾個流程比照辦理一一搬入類別中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 public class CancelSatus : PaymentSatusBase { public CancelSatus (PaymentGate g ) { _gate = g; } public override string Running (Product p ) { return "交易取消完成" ; } public override string SetSatus (PayStatus s ) { string result = string .Empty; if (s == PayStatus.Init) result = "訂單取消請勿修改" ; return result; } } public class SuccessSatus : PaymentSatusBase { public SuccessSatus (PaymentGate g ) { _gate = g; } public override string Running (Product p ) { return "交易完成" ; } public override string SetSatus (PayStatus s ) { string result = string .Empty; if (s == PayStatus.Init) result = "訂單成功請勿修改" ; return result; } }
最後外部程式使用如下
1 2 3 4 5 6 7 8 9 10 Product p = new Product(); p.Name = "電腦" ; p.Price = 300000 ; PaymentGate context = new PaymentGate(p); Console.WriteLine(context.RunProcess()); Console.WriteLine(context.RunProcess()); Console.WriteLine(context.RunProcess()); context.SetStatus(PayStatus.Init); Console.WriteLine(context.RunProcess());
程式碼放在github上
__此文作者__:Daniel Shih(石頭) __此文地址__: https://isdaniel.github.io/statuspattern/ __版權聲明__:本博客所有文章除特別聲明外,均採用 CC BY-NC-SA 3.0 TW 許可協議。轉載請註明出處!