設計模式篇——初探狀態模式

狀態模式簡介:

狀態模式允許一個對象基於內部狀態而擁有不同的行爲,這個對象看起來就好像修改了它的類。

Context將行爲委託給當前狀態對象。

把每個狀態封裝進一個類中,以此來解耦和擴展

狀態裝換可以有State類或者Context類來控制

狀態模式通常會導致設計中的類的數目大量增加

狀態類可以被多個Context實例共享。

 

  假設我麼現在有一個糖果機,可以投入“1塊錢硬幣“ ,轉動把手,彈出一顆糖果。也可以不轉動把手,把投入的錢幣退回=》

 

  我們可以從這個過程中分析得出,這個過程一共有4個狀態=》沒有投幣,已經投幣,發放(出售)糖果,糖果售罄;基於這四個狀態,也有四個動作投入錢幣,退回錢幣,轉動把手,發放糖果(這個動作是在糖果機內部完成)。

  基於這個我們創建一個GumballMachine(糖果機)類,糖果機實現了三個行爲(投幣,退幣,搖動把手),構造函數中傳入初始放入的糖果數量。糖果機中存在一個State的抽象類(下文中實現),代表所有狀態的一個父類。setState方法更新狀態。releaseBall方法彈出糖果,GetCount方法返回當前糖果數量。

 1     public class GumballMachine
 2     {
 3         //所有的狀態(初始化只用到soldOut狀態和noMoneyState狀態)
 4         static State soldOutState;  //售罄
 5         static State noMoneyState;  //沒有投幣
 6         //static State hasMoneyState; //已投幣
 7         //static State soldState;     //出售
 8 
 9         private int _count; //糖果數量
10         private State _state = soldOutState; 
11         public GumballMachine(int count)
12         {
13             _count = count;
14             soldOutState = new SoldOutState(this);
15             noMoneyState = new NoMoneyState(this);
16             //hasMoneyState = new HasMoneyState(this);
17             //soldState = new SoldState(this);
18             if (_count > 0)
19                 _state = noMoneyState;      //糖果數量大於0,初始狀態修改爲未投幣
20         }
21         public void insertMoney()           //投幣
22         {
23             _state.insertMoney();
24         }
25         public void ejectMoney()            //退幣
26         {   
27             _state.ejectMoney();
28         }
29 
30         /// <summary>
31         /// 轉動把手
32         /// </summary>
33         public void turnCrank() 
34         {
35             _state.turnCrank();         //轉動
36             _state.dispense();          //發放糖果
37         }          
38         /// <summary>
39         /// 更新狀態
40         /// </summary>
41         /// <param name="state"></param>
42         public void setState(State state)
43         {
44             _state = state;
45         }
46         /// <summary>
47         /// 彈出糖果
48         /// </summary>
49         public void releaseBall()
50         {
51             Console.WriteLine("**************彈出一顆糖果●**************");
52             if (_count != 0)
53                 _count = _count - 1;
54         }
55         /// <summary>
56         /// 返回當前糖果狀態
57         /// </summary>
58         /// <returns></returns>
59         public int GetCount()
60         {
61             return _count;
62         }
63     }
GumballMachine(糖果機)

  我們基於所有狀態抽象出一個父類State,這個父類有四個抽象方法,分別代表四個動作。(PS:這裏抽象類或者接口由使用場景選擇,抽象類可以讓所有子類都初始化一些操作。)

 1     /// <summary>
 2     /// 狀態的抽象類,所有狀態都要繼承自它
 3     /// </summary>
 4     public abstract class State
 5     {
 6         public abstract void insertMoney(); //放入硬幣
 7         public abstract void ejectMoney();  //取出硬幣
 8         public abstract void turnCrank();   //轉動把手
 9         public abstract void dispense();    //發放糖果
10     }
State抽象類

  接下來我們實現這四個狀態。

 1     /// <summary>
 2     /// 沒有投錢
 3     /// </summary>
 4     public class NoMoneyState : State
 5     {
 6         private readonly GumballMachine _GumballMachine;
 7         public NoMoneyState(GumballMachine GumballMachine)
 8         {
 9             _GumballMachine = GumballMachine;
10         }
11         public override void dispense()
12         {
13             Console.WriteLine("需要先投幣才能發放糖果");
14         }
15 
16         public override void ejectMoney()
17         {
18             Console.WriteLine("沒有投幣不能退幣");
19         }
20 
21         public override void insertMoney()
22         {
23             Console.WriteLine("投入了1塊錢,可以轉動手柄彈出糖果。");
24             _GumballMachine.setState(new HasMoneyState(_GumballMachine)); //狀態轉換
25         }
26 
27         public override void turnCrank()
28         {
29             Console.WriteLine("沒有投幣不能轉動");
30         }
31     }
沒有投幣
 1     /// <summary>
 2     /// 已經投錢
 3     /// </summary>
 4     public class HasMoneyState : State
 5     {
 6         private readonly GumballMachine _GumballMachine;
 7         public HasMoneyState(GumballMachine GumballMachine)
 8         {
 9             _GumballMachine = GumballMachine;
10         }
11         public override void insertMoney()
12         {
13             Console.WriteLine("你已經投幣,不需要重複投幣,請等待。。");
14         }
15         public override void ejectMoney()
16         {
17             Console.WriteLine("退幣。。現在機器是未投幣");
18             _GumballMachine.setState(new NoMoneyState(_GumballMachine));
19         }
20         public override void turnCrank()
21         {
22             Console.WriteLine("搖動把手");
23             _GumballMachine.setState(new SoldState(_GumballMachine));
24         }
25         public override void dispense()
26         {
27             Console.WriteLine("發放糖果中(不正確的操作。。。)");
28         }
29     }
已經投幣
 1  /// <summary>
 2     /// 發放(出售)糖果
 3     /// </summary>
 4     public class SoldState : State
 5     {
 6         private readonly GumballMachine _GumballMachine;
 7         public SoldState(GumballMachine GumballMachine)
 8         {
 9             _GumballMachine = GumballMachine;
10         }
11         public override void dispense()
12         {
13             _GumballMachine.releaseBall();  //發放糖果  ,判斷糖果數量,修改狀態
14             if (_GumballMachine.GetCount() > 0)
15                 _GumballMachine.setState(new NoMoneyState(_GumballMachine));
16             else
17             {
18                 Console.WriteLine("糖果售罄");
19                 _GumballMachine.setState(new SoldOutState(_GumballMachine));
20             }
21         }
22 
23         public override void ejectMoney()
24         {
25             Console.WriteLine("已轉動把手,不能再退幣(不正確動作)");
26         }
27 
28         public override void insertMoney()
29         {
30             Console.WriteLine("正在發放糖果,不要重複投幣(不正確動作)");
31         }
32 
33         public override void turnCrank()
34         {
35             Console.WriteLine("不要重複轉動把手(不正確操作)");
36         }
37     }
發放(出售)糖果
 1     /// <summary>
 2     /// 售罄
 3     /// </summary>
 4     public class SoldOutState : State
 5     {
 6         private readonly GumballMachine _GumballMachine;
 7         public SoldOutState(GumballMachine GumballMachine)
 8         {
 9             _GumballMachine = GumballMachine;
10         }
11         public override void dispense()
12         {
13             Console.WriteLine("糖果已售罄,不能發放糖果(不正確的操作)");
14         }
15 
16         public override void ejectMoney()
17         {
18             Console.WriteLine("請先投幣,再退幣(不正確的操作)");
19         }
20 
21         public override void insertMoney()
22         {
23             Console.WriteLine("糖果已售罄,不要再投幣(不正確的操作)");
24         }
25 
26         public override void turnCrank()
27         {
28             Console.WriteLine("請先投幣,再轉動把手(不正確的操作)");
29         }
30     }
售罄

  要注意這四個狀態都要繼承自State抽象類,它們的構造函數中均需要傳入GumballMachine,它們各個動作會使糖果機處於不同的狀態。(例如NoMoneyState(未投幣)狀態下,執行insertMoney動作後,糖果機狀態轉換爲HasMoneyState(已投幣狀態))。不同狀態中存在一些無效的操作,這裏我們直接打印了一句話,這裏根據使用場景具體分析、處理。

  接下來我們來運行一下=》我們初始化一個糖果機,放入100顆糖果。

  接下來,然我們來看下狀態模式的類圖。Context上下文中存有一個State的超類,Context將不同的行爲委託給具體的狀態(沒有投幣,已經投幣,發放(出售)糖果,糖果售罄來實現;State封裝Context的一組特定行爲。具體的狀態根據當前環境以實現不同的效果。

  

 

  這樣做的好處是將State之間的邏輯解耦。例如,我們現在要再添加一個狀態:幸運者!這樣我們的系統就變成這樣,在投幣之後,我們將有1/10的概率成爲幸運用戶,這時候糖果機將給你兩顆糖果。此時,我們添加一個狀態LuckyState(幸運用戶)。

 

 1     public class LuckyState : State            //是否是幸運
 2     {
 3         private readonly GumballMachine _GumballMachine;
 4         public LuckyState(GumballMachine GumballMachine)
 5         {
 6             _GumballMachine = GumballMachine;
 7         }
 8 
 9         public override void dispense()     
10         {
11             Console.WriteLine("你是幸運者!接下來我將給你兩顆糖");
12             _GumballMachine.releaseBall();
13             if (_GumballMachine.GetCount() == 0)                            //糖果售罄
14                 _GumballMachine.setState(new SoldOutState(_GumballMachine));
15             else
16             {
17                 _GumballMachine.releaseBall();                              //給出第二顆糖果
18                 if (_GumballMachine.GetCount() > 0)
19                     _GumballMachine.setState(new NoMoneyState(_GumballMachine));    //還有糖果,進入未投幣狀態
20                 else
21                     _GumballMachine.setState(new SoldOutState(_GumballMachine));
22 
23             }
24         }
25 
26         public override void ejectMoney()
27         {
28             Console.WriteLine("退幣");
29             _GumballMachine.setState(new NoMoneyState(_GumballMachine));
30         }
31 
32         public override void insertMoney()
33         {
34             Console.WriteLine("已投幣,不需要再次投幣(不正確的操作)");
35         }
36 
37         public override void turnCrank()
38         {
39             Console.WriteLine("你是幸運者,不需要搖動把手了,直接給你兩顆糖(不正確的操作)");
40         }
41     }
幸運用戶

  我們還要修改我們的HasMoneyState,當成爲幸運用戶的時候,糖果機變爲另外一個狀態。我們來執行下=》

   這樣我們就很容易的通過添加狀態類來擴展我們的糖果機。

最後總結一下:

  例子中的糖果機(GumballMachine)就是我們的上下文對象,然後我們的不同的狀態變化時候,糖果機(GumballMachine)就會擁有不同的狀態,不同的狀態下會使GumballMachine有不同的行爲(這樣看來我們就把我們的Context上下文對象委託給了當前的狀態對象),我們每一個狀態都有一個單獨的類,這些狀態類都繼承自State抽象類,所以我們可以隨意的替換它們。我們要擴展一個狀態也只需要添加一個繼承自State的狀態類。

  同時,這樣做的不好處就是我們的狀態類會變得很多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章