有限狀態機的好朋友:Stateless
離散數學中有提到所謂的”有限狀態機”,雖說是數學上的東西,但它很好理解也能解決一些程式上遇到的問題。
“有限狀態機”簡單說就是假設一個物體會因為不同的狀況而進入不同的狀態,最常舉的例子就是當工程師收到測試人員回報的 bug,通常這個 bug 就會進入 “Assigned” 狀態,當修好後而測試人員也測試通過,就會進入 “Closed” 狀態。.NET 平台上有一個 Stateless 這個函式庫可以用來表達”有限狀態機”,以下用電商中滿三件打折的例子來做示範。
在這裡,會需要兩個列舉型態來表示 state 跟 trigger,程式碼如下:
public enum ShoppingCartState
{
ZeroProduct,
OneProduct,
TwoProduct,
ThreeProduct
}
public enum ThreeProducts20OffTrigger
{
AddOneProduct,
SubOneProduct
}
接下來就是宣告一個 state machine 的物件並設定其 state 與 trigger 的關係,程式碼如下:
public class ShoppingCartValidator
{
private StateMachine<ShoppingCartState, ThreeProducts20OffTrigger> _stateMachine;
private int _count;
private void InitShoppingCartStateMachine()
{
_stateMachine.Configure(ShoppingCartState.ZeroProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.OneProduct)
.Ignore(ThreeProducts20OffTrigger.SubOneProduct);
_stateMachine.Configure(ShoppingCartState.OneProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.TwoProduct)
.Permit(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.ZeroProduct);
_stateMachine.Configure(ShoppingCartState.TwoProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.ThreeProduct)
.Permit(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.OneProduct);
..........
}
.....
}
前面只設定到商品兩件的狀態,因為到第三件時,有可能還會有第四件、第五件以上的狀況,而如果在滿六件後再扣掉一件,這時是不能退回到滿兩件的狀態,所以必須額外寫判斷,方式如下:
_stateMachine.Configure(ShoppingCartState.ThreeProduct)
.InternalTransition(ThreeProducts20OffTrigger.AddOneProduct, () => Console.WriteLine("aaa3"))
.OnEntry(entryAction =>
{
_count++;
Console.WriteLine("Fired!!!!!");
})
.PermitIf(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.TwoProduct, () =>
{
_count--;
if (_count <= 2)
{
return true;
}
return false;
});
_count = 2;
整個完整的寫法如下:
public class ShoppingCartValidator
{
private StateMachine<ShoppingCartState, ThreeProducts20OffTrigger> _stateMachine;
private int _count;
private void InitShoppingCartStateMachine()
{
_stateMachine.Configure(ShoppingCartState.ZeroProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.OneProduct)
.Ignore(ThreeProducts20OffTrigger.SubOneProduct);
_stateMachine.Configure(ShoppingCartState.OneProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.TwoProduct)
.Permit(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.ZeroProduct);
_stateMachine.Configure(ShoppingCartState.TwoProduct)
.Permit(ThreeProducts20OffTrigger.AddOneProduct, ShoppingCartState.ThreeProduct)
.Permit(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.OneProduct);
_stateMachine.Configure(ShoppingCartState.ThreeProduct)
.InternalTransition(ThreeProducts20OffTrigger.AddOneProduct, () => Console.WriteLine("aaa3"))
.OnEntry(entryAction =>
{
_count++;
Console.WriteLine("Fired!!!!!");
})
.PermitIf(ThreeProducts20OffTrigger.SubOneProduct, ShoppingCartState.TwoProduct, () =>
{
_count--;
if (_count <= 2)
{
return true;
}
return false;
});
_count = 2;
}
public ShoppingCartValidator()
{
_stateMachine = new StateMachine<ShoppingCartState, ThreeProducts20OffTrigger>(ShoppingCartState.ZeroProduct);
InitShoppingCartStateMachine();
}
public void AddOne()
{
_stateMachine.Fire(ThreeProducts20OffTrigger.AddOneProduct);
}
public void SubOne()
{
_stateMachine.Fire(ThreeProducts20OffTrigger.SubOneProduct);
}
public string GetState()
{
return _stateMachine.State.ToString();
}
}
使用方式如下:
ShoppingCartValidator shoppingCartValidator = new ShoppingCartValidator();
shoppingCartValidator.AddOne();
Console.WriteLine(shoppingCartValidator.GetState());
shoppingCartValidator.AddOne();
shoppingCartValidator.AddOne();
Console.WriteLine(shoppingCartValidator.GetState());
shoppingCartValidator.SubOne();
Console.WriteLine(shoppingCartValidator.GetState());
shoppingCartValidator.AddOne();
shoppingCartValidator.AddOne();
shoppingCartValidator.AddOne();
shoppingCartValidator.AddOne();
Console.WriteLine(shoppingCartValidator.GetState());
參考資料