狀態模式簡介:
狀態模式允許一個對象基於內部狀態而擁有不同的行爲,這個對象看起來就好像修改了它的類。
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 }
我們基於所有狀態抽象出一個父類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 }
接下來我們實現這四個狀態。
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的狀態類。
同時,這樣做的不好處就是我們的狀態類會變得很多。