温故知新(2)——状态模式
概述
状态模式是一种比较常用的设计模式,属于GOF23种设计模式中行为模式的一种。
允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
上面是《设计模式——可复用面向对象软件基础》一书中给出的这个模式的意图。通俗一点说就是一个对象具有多种状态,在每个状态下,对象的行为(代码逻辑)不同。对于简单的情况我们可以用if…else…或者swich语句来处理这中情况,不过当状态复杂多变,而且每个行为都因状态的不同而大不相同时,可以考虑引入状态模式来化解危机。
状态模式将每个状态和与其相关的行为封装的单独的类中,因此很容易添加新的状态;对象的状态很容易转换,可以避免在状态转换时出现不一致的情况;与状态相关的行为也可以独立的变化修改。但是状态模式引入了多个表示状态的小类,使的逻辑分散在不同的类中,不够紧凑,增加了代码阅读的难度。因此是否使用状态模式,还要权衡利弊得失而定。
结构
下面是状态模式的类图。
状态模式的结构比较简单,下面列出模式的参与者:
1、对象(书中成为环境,通常理解为包含不同状态的那个对象),提供用户感兴趣的接口,并且包含一个状态接口的引用——Context
2、对象状态的一个状态接口,用以状态与状态有关的行为——IState
3、状态的具体实现——StateA、StateB
示例
下面通过一个示例来说明状态模式的实现。假设我们有一个这样的场景:客户在我们的系统中投放广告,一般的流程是,发布——展示——展示完成。在这期间客户可以对广告进行修改、取消广告(退回支付的广告费)、终止广告(也许出现了什么法律问题),但是在广告处于不同的状态下,这些操作是不同的,具体规则如下:
1、客户可以修改已发布状态的广告;
2、客户可以取消已发布状态的广告;
3、客户不能对展示中的广告进行修改和取消操作,只能进行终止操作;
4、系统将广告由已发布变为展示中,从展示中变为展示完成,不能从已发布直接变为展示完成;
5、客户取消和终止的广告变为取消状态,正常展示完成的广告变为展示完成状态;
6、取消和展示完成为广告的最终状态不可以在做任何操作。
广告业务用UML状态图表示如下:
为了示例简单,我省略了审核等一些流程,可以看出如果使用分支判断的方式来实现,将会非常繁杂,因此此处使用状态模式是比较合适的。
1、定义状态接口IAdState,这个接口包含了用户关系的五个行为。
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 广告状态接口
7: /// </summary>
8: public interface IAdState
9: {
10: /// <summary>
11: /// 修改方法
12: /// </summary>
13: IAdState Update();
14:
15: /// <summary>
16: /// 退款(撤销)
17: /// </summary>
18: IAdState Refund();
19:
20: /// <summary>
21: /// 取消(终止)
22: /// </summary>
23: IAdState Abort();
24:
25: /// <summary>
26: /// 展示
27: /// </summary>
28: IAdState Display();
29:
30: /// <summary>
31: /// 完成展示
32: /// </summary>
33: IAdState Complet();
34: }
35: }
36:
2、具体实现状态。
PublishedState:
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 已发布
7: /// </summary>
8: public class PublishedState : IAdState
9: {
10: public IAdState Update()
11: {
12: Console.WriteLine("未展示的广告允许修改");
13: return this; //仍然是已发布状态。
14: }
15:
16: public IAdState Refund()
17: {
18: Console.WriteLine("未展示的广告允许退款");
19: return new CanceledState(); //变为取消状态
20: }
21:
22: public IAdState Abort()
23: {
24: Console.WriteLine("未展示的广告允许取消,同时自动完成退款");
25: return new CanceledState(); //变为取消状态
26: }
27:
28: public IAdState Display()
29: {
30: Console.WriteLine("将未展示的广告变为展示中状态");
31: return new DisplayingState(); //变为展示状态
32: }
33:
34: public IAdState Complet()
35: {
36: Console.WriteLine("未展示的广告不允许直接变为展示完成状态");
37: return this; //仍然是已发布状态。
38: }
39: }
40: }
41:
42:
DisplayingState:
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 展示中
7: /// </summary>
8: public class DisplayingState : IAdState
9: {
10: public IAdState Update()
11: {
12: Console.WriteLine("展示中的广告不允许修改");
13: return this; //仍然是展示中状态。
14: }
15:
16: public IAdState Refund()
17: {
18: Console.WriteLine("展示中的广告不允许退款");
19: return this; //仍然是展示中状态。
20: }
21:
22: public IAdState Abort()
23: {
24: Console.WriteLine("展示中的广告允许终止");
25: return new CanceledState(); //变为取消状态。
26: }
27:
28: public IAdState Display()
29: {
30: Console.WriteLine("广告已经处于展示中状态");
31: return this; //仍然是展示中状态。
32: }
33:
34: public IAdState Complet()
35: {
36: Console.WriteLine("广告展示完成");
37: return new CompletedState(); //变为展示完成状态。
38: }
39: }
40: }
41:
CompletedState:
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 展示完成
7: /// </summary>
8: public class CompletedState : IAdState
9: {
10: public IAdState Update()
11: {
12: Console.WriteLine("展示完成的广告不允许修改");
13: return this; //仍然是展示完成状态。
14: }
15:
16: public IAdState Refund()
17: {
18: Console.WriteLine("展示完成的广告不允许退款");
19: return this; //仍然是展示完成状态。
20: }
21:
22: public IAdState Abort()
23: {
24: Console.WriteLine("展示完成的广告不允许终止");
25: return this; //仍然是展示完成状态。
26: }
27:
28: public IAdState Display()
29: {
30: Console.WriteLine("展示完成的广告不允许展示");
31: return this; //仍然是展示完成状态。
32: }
33:
34: public IAdState Complet()
35: {
36: Console.WriteLine("已经处于展示完成状态");
37: return this; //仍然是展示完成状态。
38: }
39: }
40: }
41:
CanceledState:
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 取消
7: /// </summary>
8: public class CanceledState : IAdState
9: {
10: public IAdState Update()
11: {
12: Console.WriteLine("取消的广告不允许修改");
13: return this; //仍然是取消状态。
14: }
15:
16: public IAdState Refund()
17: {
18: Console.WriteLine("取消的广告不允许退款");
19: return this; //仍然是取消状态。
20: }
21:
22: public IAdState Abort()
23: {
24: Console.WriteLine("取消的广告不允许终止");
25: return this; //仍然是取消状态。
26: }
27:
28: public IAdState Display()
29: {
30: Console.WriteLine("取消的广告不允许展示");
31: return this; //仍然是取消状态。
32: }
33:
34: public IAdState Complet()
35: {
36: Console.WriteLine("取消的广告不允许变为完成展示状态");
37: return this; //仍然是取消状态。
38: }
39: }
40: }
41:
3、包含状态的广告类Ad。
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: /// <summary>
6: /// 广告类
7: /// </summary>
8: public class Ad
9: {
10: public Ad()
11: : this(new PublishedState())
12: { }
13:
14: public Ad(IAdState state)
15: {
16: this.state = state;
17: }
18:
19: private IAdState state;
20:
21: public IAdState State
22: {
23: get { return state; }
24: }
25:
26: /// <summary>
27: /// 修改方法
28: /// </summary>
29: public void Update()
30: {
31: this.state = this.state.Update();
32: }
33:
34: /// <summary>
35: /// 退款(撤销)
36: /// </summary>
37: public void Refund()
38: {
39: this.state = this.state.Refund();
40: }
41:
42: /// <summary>
43: /// 取消(终止)
44: /// </summary>
45: public void Abort()
46: {
47: this.state = this.state.Abort();
48: }
49:
50: /// <summary>
51: /// 展示
52: /// </summary>
53: public void Display()
54: {
55: this.state = this.state.Display();
56: }
57:
58: /// <summary>
59: /// 完成展示
60: /// </summary>
61: public void Complet()
62: {
63: this.state = this.state.Complet();
64: }
65: }
66: }
67:
4、为了测试我们完成一些测试代码。
1: using System;
2:
3: namespace DesignPatterns.State
4: {
5: class Program
6: {
7: static void Main(string[] args)
8: {
9: Ad ad = new Ad();
10: //对广告进行一些操作
11: ad.Complet();
12: ad.Update();
13: ad.Display();
14: ad.Update();
15: ad.Complet();
16: ad.Refund();
17:
18: Console.WriteLine("===按任意键结束...===");
19: Console.ReadKey();
20: }
21: }
22: }
23:
5、查看运行结果。
在测试代码中任意对广告进行操作,可以查看到不同的操作结果。思考一下,当流程改变是如何进行修改。