12 状态模式
状态模式(State)定义:允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。
UML类图:
状态模式适用于要转换很多业务状态的场景。比如,Head First举的糖果机例子,
糖果机有“没有投币”、“有投币”、“售出糖果”、“糖果售罄”四种状态,并且有四种动作:“投入硬币”、“返回硬币”、“转动曲柄”、“发放糖果”。四种动作与四种状态之间的关系比较复杂,但上图可以清晰地表达出这种关系。
如果按照面向过程的实现方法,要表达出这种关系,需要一大堆的if else分支(4*4=16个),更麻烦的是,如果有新的状态加入,要修改的地方太多,违背了开放-封闭原则。
而采用状态模式可以部分地解决这个问题。
状态模式将每个状态封装成一个类,四种动作便成了类的四个方法,不同的类对这些方法有不同的实现,实现了状态类之间的自动切换。看起来很有《失控》中,那只用去中心化思维制造的机器昆虫,没有大脑,只为每只脚设定了动作规则,就可以完美运行。
代码如下:
先是规定了四种动作的接口:
1 interface State 2 { 3 void InsertQuarter(); 4 void EjectQuarter(); 5 void TurnQuarter(); 6 void Dispense(); 7 }
自带逻辑的四种状态如下(五种状态,这里多了赢家状态,赢家状态会一次吐出两颗糖果):
1 class NoQuarterState:State 2 { 3 GumballMachine gumballMachine; 4 public NoQuarterState(GumballMachine g) 5 { 6 gumballMachine = g; 7 } 8 public void InsertQuarter() 9 { 10 Console.WriteLine("You inserted a quarter"); 11 gumballMachine.SetState(gumballMachine.GetHasQuarterState()); 12 } 13 14 public void EjectQuarter() 15 { 16 Console.WriteLine("You haven't inserted a quarter"); 17 } 18 19 public void TurnQuarter() 20 { 21 Console.WriteLine("You turned,but there's no quarter"); 22 } 23 24 public void Dispense() 25 { 26 Console.WriteLine("You need to pay first"); 27 } 28 } 29 30 // 31 class HasQuarterState : State 32 { 33 Random randomWinner = new Random(DateTime.Now.Millisecond); 34 GumballMachine gumballMachine; 35 public HasQuarterState(GumballMachine g) 36 { 37 gumballMachine = g; 38 } 39 public void InsertQuarter() 40 { 41 Console.WriteLine("You can't insert another quarter"); 42 } 43 44 public void EjectQuarter() 45 { 46 Console.WriteLine("Quarter returned"); 47 } 48 49 public void TurnQuarter() 50 { 51 52 Console.WriteLine("You turned"); 53 int randomNum = randomWinner.Next(); 54 if ((randomNum % 2 == 0) && (gumballMachine.GetCurrentCount() > 0)) 55 { 56 gumballMachine.SetState(gumballMachine.GetWinnerState()); 57 } 58 else 59 { 60 gumballMachine.SetState(gumballMachine.GetSoldState()); 61 } 62 63 } 64 65 public void Dispense() 66 { 67 Console.WriteLine("No gumball dispensed"); 68 } 69 } 70 // 71 class SoldState : State 72 { 73 GumballMachine gumballMachine; 74 public SoldState(GumballMachine g) 75 { 76 gumballMachine = g; 77 } 78 public void InsertQuarter() 79 { 80 Console.WriteLine("Please wait,we're already giving you a gumball"); 81 } 82 83 public void EjectQuarter() 84 { 85 Console.WriteLine("Sorry, you already turned the crank"); 86 } 87 88 public void TurnQuarter() 89 { 90 Console.WriteLine("Turning twice doesn't get you another gumball"); 91 } 92 93 public void Dispense() 94 { 95 gumballMachine.ReleaseBall(); 96 if (gumballMachine.GetCurrentCount() > 0) 97 { 98 gumballMachine.SetState(gumballMachine.GetNoQuarterState()); 99 } 100 else 101 { 102 Console.WriteLine("Oops,out of gumballs"); 103 gumballMachine.SetState(gumballMachine.GetSoldOutState()); 104 } 105 } 106 } 107 // 108 class SoldOutState:State 109 { 110 GumballMachine gumballMachine; 111 public SoldOutState(GumballMachine g) 112 { 113 gumballMachine = g; 114 } 115 public void InsertQuarter() 116 { 117 Console.WriteLine("There is not gumballs"); 118 } 119 120 public void EjectQuarter() 121 { 122 Console.WriteLine("You haven't inject a gumball"); 123 } 124 125 public void TurnQuarter() 126 { 127 Console.WriteLine("Be quiet"); 128 } 129 130 public void Dispense() 131 { 132 Console.WriteLine("No quarter anymore"); 133 } 134 }
糖果机代码如下:
1 class GumballMachine 2 { 3 State soldOutState; 4 State noQuarterState; 5 State hasQuarterState; 6 State soldState; 7 State winnerState; 8 State state; 9 int count = 0; 10 public GumballMachine(int numberGumballs) 11 { 12 soldOutState = new SoldOutState(this); 13 noQuarterState = new NoQuarterState(this); 14 hasQuarterState = new HasQuarterState(this); 15 soldState = new SoldState(this); 16 winnerState = new WinnerState(this); 17 this.count = numberGumballs; 18 if (numberGumballs > 0) 19 { 20 state = noQuarterState; 21 } 22 else 23 { 24 state = soldOutState; 25 } 26 } 27 28 public void InsertQuarter() 29 { 30 state.InsertQuarter(); 31 } 32 public void EjectQuarter() 33 { 34 state.EjectQuarter(); 35 } 36 public void TurnCrank() 37 { 38 state.TurnQuarter(); 39 state.Dispense(); 40 } 41 public void SetState(State s) 42 { 43 state = s; 44 } 45 public void ReleaseBall() 46 { 47 Console.WriteLine("A gumball comes rolling out the slot"); 48 if (count != 0) count--; 49 } 50 51 public State GetSoldOutState() 52 { 53 return soldOutState; 54 } 55 public State GetNoQuarterState() 56 { 57 return noQuarterState; 58 } 59 public State GetHasQuarterState() 60 { 61 return hasQuarterState; 62 } 63 public State GetSoldState() 64 { 65 return soldState; 66 } 67 public State GetWinnerState() 68 { 69 return winnerState; 70 } 71 public int GetCurrentCount() 72 { 73 return count; 74 } 75 }
这里状态的转换由State类内部控制,但有可由Context就是这儿的Main函数中直接控制,可根据具体需求决定采用哪种方式。
使用状态模式通常会导致设计中类的数目大量增加。但相比很多if else分支来说,还是可取的,而且这些类并不对外可见。
状态模式与策略模式的区别:
两者的类图相同,但区别在于意图不同。
状态模式允许Context随着状态的改变而改变行为,但策略模式则用行为或算法来配置Context类。状态模式的状态变化逻辑已经内化,但策略模式使用哪种算法,可由外部决定。