设计模式篇——初探状态模式
分类:
IT文章
•
2023-12-30 09:37:30
状态模式简介:
状态模式允许一个对象基于内部状态而拥有不同的行为,这个对象看起来就好像修改了它的类。
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的状态类。
同时,这样做的不好处就是我们的状态类会变得很多。