前言
状态模式,对象行为型模式的一种。在《设计模式 - 可复用的面向对象软件》一书中将之描述为“ 允许一个对象在其内部状态改变时改变它的行为,使对象看起来似乎修改了它的类 ”。
场景
我们都坐过火车,火车可以简单的分为“ 开门 ”,“ 关门 ”,“ 运行 ”,“ 停止 ”四个状态。火车在这四个状态下分别可以做不同的事情。比如只有在关门时才行运行、只有在停止时才能开门。
我们在开发类似的业务时,往往会在“ 火车对象 ”中保持一个属性来标识当前的状态,并且在执行相应操作时去验证这个状态。
public class Train { public TrainState State { set; get; } public void OpenDoor() { if(this.State == TrainState.OpenDoor) { Console.WriteLine("火车门已经打开"); } else if(this.State == TrainState.CloseDoor || this.State == TrainState.Stop) { Console.WriteLine("正在打开火车门"); } else if (this.State == TrainState.Run) { Console.WriteLine("火车正在运行中,不可打开火车门"); } } } public enum TrainState { OpenDoor, CloseDoor, Run, Stop }
在这种情况下,往往需要通过if-else或switch-case语句来做判断。大量if-else语句的使用使得代码的可阅读性很差,扩展起来的麻烦的很,并且使用属性来标识对象的状态也不够明确。为了避免这种情况,可使用状态模式来明确对象的状态并避免过多的使用if-else语句,让我们的代码更加优雅。
结构
- Context(上下文):定义用户感兴趣的功能。保持State的引用,并将用户的请求转发给State去处理;
- State(状态接口):用来定义与Context的一个特定状态相关的行为;
- ConcreteState(具体状态):实现与Context的一个特定状态相关的行为;
示例
以场景中提到的火车为例,我们首先需要为火车的每个状态创建一个实现IState接口的类,以及一个上下文类Train来表示火车。如下。
public interface IState { Train Context { get; } void OpenDoor(); void CloseDoor(); void Run(); void Stop(); } public class OpenDoorState : IState { public Train Context { private set; get; } public OpenDoorState(Train context) { this.Context = context; } public void OpenDoor() { //当前状态已经是“开门”状态,不做任何操作 } public void CloseDoor() { Console.WriteLine("[OpenDoorState]:关门"); this.Context.State = TrainStateFactory.Instance.CreateCloseDoorState(this.Context); } public void Run() { throw new Exception("[OpenDoorState]:开门状态下禁止运行"); } public void Stop() { //该操作只能在“运行”状态时使用,不做任何操作 } } public class CloseDoorState : IState { public Train Context { private set; get; } public CloseDoorState(Train context) { this.Context = context; } public void OpenDoor() { Console.WriteLine("[CloseDoorState]:开门"); this.Context.State = TrainStateFactory.Instance.CreateOpenDoorState(this.Context); } public void CloseDoor() { //当前状态已经是“关门”状态,不做任何操作 } public void Run() { Console.WriteLine("[CloseDoorState]:开车"); this.Context.State = TrainStateFactory.Instance.CreateRunState(this.Context); } public void Stop() { //该操作只能在“运行”状态时使用,不做任何操作 } } public class RunState : IState { public Train Context { private set; get; } public RunState(Train context) { this.Context = context; } public void OpenDoor() { throw new Exception("[RunState]:运行状态下禁止开门"); } public void CloseDoor() { //该操作只能在“开门”状态时使用,不做任何操作 } public void Run() { //当前状态已经是“运行”状态,不做任何操作 } public void Stop() { Console.WriteLine("[RunState]:停车"); this.Context.State = TrainStateFactory.Instance.CreateStopState(this.Context); } } public class StopState : IState { public Train Context { private set; get; } public StopState(Train context) { this.Context = context; } public void OpenDoor() { Console.WriteLine("[StopState]:开门"); this.Context.State = TrainStateFactory.Instance.CreateOpenDoorState(this.Context); } public void CloseDoor() { //该操作只能在“开门”状态时使用,不做任何操作 } public void Run() { //该操作只能在“关门”状态时使用,不做任何操作 } public void Stop() { //当前状态已经是“停止”状态,不做任何操作 } } public class Train { private IState _stateObj = null; public IState State { set => this._stateObj = value; } public void OpenDoor() { this._stateObj.OpenDoor(); } public void CloseDoor() { this._stateObj.CloseDoor(); } public void Run() { this._stateObj.Run(); } public void Stop() { this._stateObj.Stop(); } } /// <summary> /// 火车状态的享元工厂 /// </summary> public class TrainStateFactory { //单例模式 private TrainStateFactory() { } private static TrainStateFactory _instance = null; private static object _sysLock = new object(); public static TrainStateFactory Instance { get { if(_instance == null) { lock (_sysLock) { if(_instance == null) { _instance = new TrainStateFactory(); } } } return _instance; } } /// <summary> /// 状态的享元池 /// </summary> private List<IState> StatePool { set; get; } = new List<IState>(); public IState CreateOpenDoorState(Train context) { var state = this.StatePool.Find(t => object.ReferenceEquals(t.Context, context) && t is OpenDoorState); if(state == null) { state = new OpenDoorState(context); this.StatePool.Add(state); } return state; } public IState CreateCloseDoorState(Train context) { var state = this.StatePool.Find(t => object.ReferenceEquals(t.Context, context) && t is CloseDoorState); if (state == null) { state = new CloseDoorState(context); this.StatePool.Add(state); } return state; } public IState CreateRunState(Train context) { var state = this.StatePool.Find(t => object.ReferenceEquals(t.Context, context) && t is RunState); if (state == null) { state = new RunState(context); this.StatePool.Add(state); } return state; } public IState CreateStopState(Train context) { var state = this.StatePool.Find(t => object.ReferenceEquals(t.Context, context) && t is StopState); if (state == null) { state = new StopState(context); this.StatePool.Add(state); } return state; } }
示例中,定义了四个实现了IState接口的类OpenDoorState、CloseDoorState、RunState和StopState来表示火车Train的4个基本状态。同时在这些状态类中保留了Train的引用以便在执行操作后修改Train的状态(这样做的好处是可以简化Train,使其只需要关心当前要执行的操作而不必关系当前的状态,但同时也意味着IState的实现类需要保留与其本身无关的引用)。由状态类来修改Train的状态类似观察者模式(状态类发布,Train类订阅),在此基础上我们增加了一个创建火车状态的享元工厂TrainStateFactory,因为在本例中IState的实现类只存在行为而并没有内部状态,所以可以将其作为享元共享。
总结
状态模式可以帮助我们减少条件语句if-else和switch-case的使用,将每一个条件分支(状态)封装到一个独立的类中,使得对象(封装了条件分支的类)可以不依赖其它的对象而独立的变化,并且可以很容易的增加新的状态,遵循了迪米特法则。但状态模式对开闭原则并不友好。无论如何,在增加新的状态时都需要修改那些负责转换状态的逻辑,也必然会增加系统中类和对象的个数。因为状态模式的结构与实现都较为复杂,所以增加了维护的难度。
以上,就是我对状态模式的理解,希望对你有所帮助。
示例源码:https://gitee.com/wxingChen/DesignPatternsPractice
系列汇总:https://www.cnblogs.com/wxingchen/p/10031592.html
本文著作权归本人所有,如需转载请标明本文链接(https://www.cnblogs.com/wxingchen/p/10208275.html)