離散數學中有提到所謂的”有限狀態機”,雖說是數學上的東西,但它很好理解也能解決一些程式上遇到的問題。

“有限狀態機”簡單說就是假設一個物體會因為不同的狀況而進入不同的狀態,最常舉的例子就是當工程師收到測試人員回報的 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());

參考資料