假设某人一整天的流程就是“吃饭→工作→睡觉”,并且他要严格按照这个流程来,那也就是说在吃饭状态下,他只能做跟吃饭有关的事情,而不能越界做工作或者睡觉有关的事情,这就涉及到了状态的管理。在写代码的时候,我们常常要根据当前的状态,来决定下一个状态并且确定当前状态下要做什么样的事情。一般来讲,我们会写一系列的if-else if-else if-else语句来实现,但是要是状态非常多的时候,这种代码不仅仅看起来十分长,而且看的人生理上也会有一种厌恶感并且看一点估计忘一点。导致这种臭代码的原因就是,我们一口气在一个函数里面管理了太多种的状态。于是,有人就提出了,能不能让状态自己管理自己。也就有点像我们的行政制度中自治区,我们不再统一地在一个函数里面管理N种状态,而是让某个状态管理它自己本身。
经典的状态管理模式如下图所示
让我来解释一下这幅图的意思,IState依赖于IContext,也就是IContext对应的实体类拥有一个IState类型的字段,在IContext类里面的Request()方法里面调用IState中的Handle方法。我们可以通过改变IContext里面的IState实体类类型来改变状态,从而实现根据不同的状态使用不同的方法。
下面让我们来段代码说明一下。以下代码模拟数据库的连接、关闭。连接和关闭两个状态分别有两个对应的状态类与之对应。
1、首先我们还是先定义状态的接口,另外定义一个抽象类,用来表示数据库连接类。
/// <summary> /// 数据库连接状态 /// </summary> public interface IConnectionState { void Open(); void Close(); void Query(); }
在定义一个数据库连接对象的抽象基类
abstract class ContextBase { private IConnectionState connectionState; public IConnectionState ConnectionState { get { return connectionState; } set { connectionState = value; } } public virtual void Open() { this.connectionState.Open(); } public virtual void Close() { this.connectionState.Close(); } public virtual void Query() { this.connectionState.Query(); } }
2、我们再定义两个具体类OpenState、CloseState,实现IConnectionState接口,分别代表数据库开、关两种状态。并且在状态类内部,如OpenState类的内部规定了在Opne状态下,可以执行的操作和不能够执行的操作。
/// <summary> /// 打开状态 /// </summary> class OpenState : IConnectionState { public void Open() { throw new NotImplementedException("打开状态下,不能再打开数据库连接"); } public void Close() { //打开状态下,可以关闭数据库连接 } public void Query() { //打开状态下,可以查询 } } /// <summary> /// 关闭状态 /// </summary> class CloseState : IConnectionState { public void Open() { //关闭状态下可以重新打开连接 } public void Close() { throw new NotImplementedException("关闭状态下,不能再关闭数据库连接"); } public void Query() { throw new NotImplementedException("关闭状态下,不能查询数据库"); } }
3、下面我们用一个类来继承抽象类,并进行测试。具体的类实现方式见下方,这里只给出关键函数。
public void ConnectionStateTest() { ContextBase target = CreateContextBase(); target.ConnectionState = new OpenState(); try { target.Open();//执行Open操作,遇到异常,不执行 Assert.IsTrue(false); Assert.IsTrue(false); } catch (Exception ex) { } target.Close();//在打开状态下,正常关闭,并执行Assert.IsTrue(false); Assert.IsTrue(false); target.ConnectionState = new CloseState(); try { target.Close(); Assert.IsTrue(false); } catch (Exception ex) { } target.Open(); Assert.IsTrue(false); }
从以上代码,我们可以知道,原先需要用讨厌的if-else if-else if-else管理的讨厌的长长的状态管理函数,已经被我们封装在一个个的小状态管理类里面了,并且这些状态管理类可以自己管理自己什么状态应该做什么样的事情。
1 using BangWorks.PractcalPattern.States.Classic; 2 using Microsoft.VisualStudio.TestTools.UnitTesting; 3 using System; 4 5 namespace BangWork.PractcalPattern.Concept.InderTest 6 { 7 8 9 /// <summary> 10 ///这是 ContextBaseTest 的测试类,旨在 11 ///包含所有 ContextBaseTest 单元测试 12 ///</summary> 13 [TestClass()] 14 public class ContextBaseTest : ContextBase 15 { 16 /// <summary> 17 /// 打开状态 18 /// </summary> 19 class OpenState : IConnectionState 20 { 21 22 public void Open() 23 { 24 throw new NotImplementedException("打开状态下,不能再打开数据库连接"); 25 } 26 27 public void Close() 28 { 29 //打开状态下,可以关闭数据库连接 30 } 31 32 public void Query() 33 { 34 //打开状态下,可以查询 35 } 36 } 37 /// <summary> 38 /// 关闭状态 39 /// </summary> 40 class CloseState : IConnectionState 41 { 42 43 public void Open() 44 { 45 //关闭状态下可以重新打开连接 46 } 47 48 public void Close() 49 { 50 throw new NotImplementedException("关闭状态下,不能再关闭数据库连接"); 51 } 52 53 public void Query() 54 { 55 throw new NotImplementedException("关闭状态下,不能查询数据库"); 56 } 57 } 58 private TestContext testContextInstance; 59 60 /// <summary> 61 ///获取或设置测试上下文,上下文提供 62 ///有关当前测试运行及其功能的信息。 63 ///</summary> 64 public TestContext TestContext 65 { 66 get 67 { 68 return testContextInstance; 69 } 70 set 71 { 72 testContextInstance = value; 73 } 74 } 75 76 #region 附加测试特性 77 // 78 //编写测试时,还可使用以下特性: 79 // 80 //使用 ClassInitialize 在运行类中的第一个测试前先运行代码 81 //[ClassInitialize()] 82 //public static void MyClassInitialize(TestContext testContext) 83 //{ 84 //} 85 // 86 //使用 ClassCleanup 在运行完类中的所有测试后再运行代码 87 //[ClassCleanup()] 88 //public static void MyClassCleanup() 89 //{ 90 //} 91 // 92 //使用 TestInitialize 在运行每个测试前先运行代码 93 //[TestInitialize()] 94 //public void MyTestInitialize() 95 //{ 96 //} 97 // 98 //使用 TestCleanup 在运行完每个测试后运行代码 99 //[TestCleanup()] 100 //public void MyTestCleanup() 101 //{ 102 //} 103 // 104 #endregion 105 106 107 internal virtual ContextBase CreateContextBase() 108 { 109 // TODO: 实例化相应的具体类。 110 ContextBase target = new ContextBaseTest(); 111 return target; 112 } 113 114 /// <summary> 115 ///Close 的测试 116 ///</summary> 117 [TestMethod()] 118 public void CloseTest() 119 { 120 ContextBase target = CreateContextBase(); // TODO: 初始化为适当的值 121 target.Close(); 122 Assert.Inconclusive("无法验证不返回值的方法。"); 123 } 124 125 /// <summary> 126 ///Open 的测试 127 ///</summary> 128 [TestMethod()] 129 public void OpenTest() 130 { 131 ContextBase target = CreateContextBase(); // TODO: 初始化为适当的值 132 target.Open(); 133 Assert.Inconclusive("无法验证不返回值的方法。"); 134 } 135 136 /// <summary> 137 ///Query 的测试 138 ///</summary> 139 [TestMethod()] 140 public void QueryTest() 141 { 142 ContextBase target = CreateContextBase(); // TODO: 初始化为适当的值 143 target.Query(); 144 Assert.Inconclusive("无法验证不返回值的方法。"); 145 } 146 147 /// <summary> 148 ///ConnectionState 的测试 149 ///</summary> 150 [TestMethod()] 151 public void ConnectionStateTest() 152 { 153 ContextBase target = CreateContextBase(); // TODO: 初始化为适当的值 154 155 target.ConnectionState = new OpenState(); 156 try 157 { 158 target.Open();//执行Open操作,遇到异常,不执行 Assert.IsTrue(false); 159 Assert.IsTrue(false); 160 } 161 catch (Exception ex) 162 { 163 164 } 165 target.Close();//在打开状态下,正常关闭,并执行Assert.IsTrue(false); 166 Assert.IsTrue(false); 167 168 target.ConnectionState = new CloseState(); 169 try 170 { 171 target.Close(); 172 Assert.IsTrue(false); 173 } 174 catch (Exception ex) 175 { 176 } 177 178 target.Open(); 179 Assert.IsTrue(false); 180 } 181 } 182 }