状态模式简介:
状态模式允许一个对象基于内部状态而拥有不同的行为,这个对象看起来就好像修改了它的类。
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的状态类。
同时,这样做的不好处就是我们的状态类会变得很多。