首页 > 代码库 > 12 状态模式

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     }
View Code

 

自带逻辑的四种状态如下(五种状态,这里多了赢家状态,赢家状态会一次吐出两颗糖果):

技术分享
  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     }
View Code

 

糖果机代码如下:

技术分享
 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     }
View Code

 

这里状态的转换由State类内部控制,但有可由Context就是这儿的Main函数中直接控制,可根据具体需求决定采用哪种方式。

使用状态模式通常会导致设计中类的数目大量增加。但相比很多if else分支来说,还是可取的,而且这些类并不对外可见。

状态模式与策略模式的区别:

两者的类图相同,但区别在于意图不同。

状态模式允许Context随着状态的改变而改变行为,但策略模式则用行为或算法来配置Context类。状态模式的状态变化逻辑已经内化,但策略模式使用哪种算法,可由外部决定。

 

12 状态模式