说到状态模式,顾名思义,应该就是跟状态相关的设计模式了,不过,咱们仍是跟前面同样,先无论状态模式是个什么东西,先从一个小小的例子出发,看看状态模式能为咱们解决什么问题。git
如今须要实现一个交通灯调度程序,交通灯的颜色须要在红灯->绿灯->黄灯->红灯之间循环转换,可是不容许绿灯->红灯或黄灯->绿灯等状况。这属于交通规则的常识,如今咱们用程序实现它,先看看咱们最传统的思考和实现方式。github
首先,咱们会很容易想到须要定义一个交通灯颜色的枚举:算法
public enum LightColor { Green, Red, Yellow }
而后,定义一个交通灯的类,在交通灯类中处理颜色转换及相应的业务逻辑,代码以下:设计模式
public class TrafficLight { private LightColor _lightColor; public TrafficLight() { _lightColor = LightColor.Red; } public void Turn() { if (_lightColor == LightColor.Red) { Console.WriteLine("红灯停"); _lightColor = LightColor.Green; } else if (_lightColor == LightColor.Green) { Console.WriteLine("绿灯行"); _lightColor = LightColor.Yellow; } else if (_lightColor == LightColor.Yellow) { Console.WriteLine("黄灯亮了等一等"); _lightColor = LightColor.Red; } } }
最后,再不妨调用运行一下:并发
static void Main(string[] args) { TrafficLight light = new TrafficLight(); light.Turn(); light.Turn(); light.Turn(); light.Turn(); }
显而易见,这段代码是彻底知足需求的,而且逻辑严谨,调用方式也极其简单,若是需求不变,这或许就是最好的实现方式了。可是通过前面设计原则的熏陶,咱们知道,需求不变是不可能的。所以,咱们很容易就会发现这段代码存在的问题,充斥着if-else
的条件分支,这就意味着扩展困难。这里例子简单,可能并不明显,可是真实项目中必然会有更多的条件分支和更多相似Turn()
的方法,这会致使整个项目扩展维护起来极其困难,由于,它严重违背了开闭原则。ide
其实,对于解决if-else
或switch-case
带来的问题,咱们已经至关有经验了,在简单工厂模式中,咱们采用工厂方法模式抽象出生产具体类的工厂类解决了switch-case
的问题,在上一篇的策略模式中,咱们经过将方法抽象成策略类的方式,一样解决了switch-case
的问题。这里也不例外,咱们也必定须要抽象点什么才行。可是具体抽象什么呢?灯的颜色?Turn()
方法?仍是别的什么?思路好像并非那么清晰。不过呢,咱们发现其实这段代码结构跟策略模式改造前的例子极其类似,咱们不妨用策略模式改造一下,看看可否知足需求,若是不知足,看看还缺点什么,而后再进一步改造,由于咱们知道,策略模式至少能解决if-else
或switch-case
的问题。this
咱们看看策略模式改造后的代码,先将Turn()
方法抽象成策略类:spa
public interface ITurnStrategy { void Turn(); } public class GreenLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("绿灯行"); } } public class RedLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("红灯停"); } } public class YellowLightTurnStrategy : ITurnStrategy { public void Turn() { Console.WriteLine("黄灯亮了等一等"); } }
再看看改造后的TrafficLight
类:设计
public class TrafficLight { private ITurnStrategy _turnStrategy; public TrafficLight(ITurnStrategy turnStrategy) { _turnStrategy = turnStrategy; } public void Turn() { if (_turnStrategy != null) { _turnStrategy.Turn(); } } public void ChangeTurnStrategy(ITurnStrategy turnStrategy) { _turnStrategy = turnStrategy; } }
一切看起来彷佛都很完美,完美无缺。再来看看如何使用:code
static void Main(string[] args) { TrafficLight light = new TrafficLight(new RedLightTurnStrategy()); light.Turn(); light.ChangeTurnStrategy(new GreenLightTurnStrategy()); light.Turn(); light.ChangeTurnStrategy(new YellowLightTurnStrategy()); light.Turn(); light.Turn(); }
一用就发现了问题,调用变复杂了。其实,为了能让系统更容易扩展,调用时复杂一点也没什么,可是,另外一个致命的问题却不能忽视,咱们但愿灯颜色切换是由内部一套固定机制控制,而不是调用方来决定,若是用户想换什么颜色就换什么颜色,交通规则岂不乱套了?显然,策略模式是不知足需求的,咱们其实但愿light.ChangeTurnStrategy()
这个动做,由系统本身内部完成。
既然不知足需求,那么问题到底出在哪呢?回过头来再梳理一下,咱们发现或许咱们的思路一开始就出现了误差,交通灯能换颜色吗?显然是不能的,由于每一个灯的颜色是固定的,咱们所谓的换颜色,实际上换的是灯,难道要用工厂方法模式来创造不一样颜色的灯?显然也不合适,三个灯一开始就在那里,只是循环切换而已,不存在建立的过程。实际上,咱们或许应该换一种思路,这里明显体现的是交通灯的三种状态,每一种状态下对应一种须要处理的行为动做,同时,也只有状态才有切换的过程。
换一种思路后,咱们看问题的角度就不同了,看看改变思路后的代码:
public abstract class TrafficLightState { public abstract void Handle(TrafficLight light); } public class GreenState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("绿灯行"); light.SetState(new YellowState()); } } public class RedState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("红灯停"); light.SetState(new GreenState()); } } public class YellowState : TrafficLightState { public override void Handle(TrafficLight light) { Console.WriteLine("黄灯亮了等一等"); light.SetState(new RedState()); } } public class TrafficLight { private TrafficLightState _currentState; public TrafficLight() { _currentState = new RedState(); } public void Turn() { if (_currentState != null) { _currentState.Handle(this); } } public void SetState(TrafficLightState state) { _currentState = state; } }
其实,能够发现,除了类名和方法名变了,代码跟策略模式几乎如出一辙(具体演化过程,文字难以表达清楚,能够看一下我在B站或者公众号上的视频),但含义倒是天差地远,这里不是直接将方法抽象成策略对象,而是抽象不一样的状态,所以用了抽象类,而非接口(也能够用接口,可是咱们一般会将方法抽象成接口,而将对象或属性抽象成类);并为每一个状态提供处理该状态下对应行为的接口方法,而不是直接提供具体行为的接口方法。
另外,参数也有所不一样,TrafficLightState
中须要持有对TrafficLight
的引用,由于须要在具体的状态类中处理TrafficLight
的状态转移。改造后的代码再次完美知足需求,调用方又变得简单了,状态的转移再次回归了主权:
static void Main(string[] args) { TrafficLight light = new TrafficLight(); light.Turn(); light.Turn(); light.Turn(); light.Turn(); }
这就状态模式了,下面是交通灯示例最终的类图:
从上面的例子中,咱们可能会很容易联想到状态机,咱们也常常听到或看到有限状态机或无限状态机这样的字眼,那么有限状态机跟状态模式有什么关系呢?咱们先看看有限状态机的工做原理。有限状态机的工做原理是,发生 事件(event) 后,根据 当前状态(cur_state) ,决定执行的 动做(action) ,并设置 下一个状态(nxt_state)。从交通灯例子能够看到,事件(event) 就是TrafficLight
中的Turn()
方法,由客户端触发,触发后,系统会判断当前处于哪一种灯的状态,而后执行相应的动做,完成以后再设置下一种灯状态,和有限状态机的工做原理完美对应上了。那么,两者是否等价呢?其实否则,状态模式只是实现有限状态机的一种手段而已,由于if-else
版本的实现,也是有限状态机。
这里算是一个小插曲,下面咱们回归到状态模式。
状态模式容许一个对象在其内部状态改变时改变它的行为,从而使对象看起来彷佛修改了它的类。
咱们将交通灯示例的类图抽象一下,就能够获得以下状态模式的类图:
ConcreteState
对象来处理;switch-case
、if-else
带来的难以维护的问题,这个很明显,没什么好说的;这个问题须要单独说,咱们不难发现,状态模式虽然解决了不少问题,可是每次状态的切换都须要建立一个新的状态类,而本来它仅仅是一个小小的枚举值而已,这样一对比,对象重复的建立资源开销是否过于巨大?其实,要解决对象重复建立的问题,咱们知道,单例模式和享元模式都是不错的选择,具体选用哪个,就要看状态类的数量和我的的喜爱了。
下面是采用享元模式改进的代码,首先是熟悉的享元工厂,代码很简单:
public class LightStateFactory { private static readonly IDictionary<Type, TrafficLightState> _lightStates = new Dictionary<Type, TrafficLightState>(); private static readonly object _locker = new object(); public static TrafficLightState GetLightState<TLightState>() where TLightState : TrafficLightState { Type type = typeof(TLightState); if (!_lightStates.ContainsKey(type)) { lock (_locker) { if (!_lightStates.ContainsKey(type)) { TrafficLightState typeface = Activator.CreateInstance(typeof(TLightState)) as TrafficLightState; _lightStates.Add(type, typeface); } } } return _lightStates[type]; } }
使用就更简单了,将建立状态对象的地方换成享元工厂建立就能够了,代码片断以下:
public override void Handle(TrafficLight light) { Console.WriteLine("红灯停"); light.SetState(LightStateFactory.GetLightState<GreenState>()); }
这里须要特别提一下,因为状态是单例的,能够在多个上下文间共享,而任什么时候候,涉及到全局共享就不得不考虑并发的问题。所以,除非明确须要共享,不然状态类中不该持有其它的资源,否则可能产生并发问题。一样的缘由,状态类也不要经过属性或字段的方式持有对Context的引用,这也是我采用局部变量对TrafficLight
进行传参的缘由。
其实,从类图和实现方式上能够看出,状态模式和策略模式真的很像,可是因为策略模式更具备通常性,所以更容易想到。并且,咱们也知道状态模式和策略模式都能解决if-else
带来的问题,关键就在于策略和状态的识别,就如上述交通灯例子,刚开始识别成策略也很难发现有什么不对。再举一个更通俗的例子,老师会根据同窗的考试成绩对同窗给出不一样的奖惩方案,如成绩低于60分的同窗罚款,成绩高于90分的同窗奖钱,可是怎么奖怎么罚,都是老师决定的(否则全考90分以上,老师得哭)。这里是普通的条件分支,没有枚举,可是咱们依然能够看出,这里体现的是根据不一样的分数段采起不一样的策略,能够采用策略模式。再例如,一样是考试成绩,父母对你设置一个指标,考了60如下,罚钱,考了90分以上,奖钱。这时是策略仍是状态呢?感受好像均可以,但实际上,仔细思考会发现,或许视为状态会更好,即在不一样的状态会有一个对应的动做,但状态的有哪些呢?分数段?奖罚?状态又是怎么转移的呢?还得仔细斟酌,这里例子简单,或许能想清楚(其实不必定),但实际项目中,估计就没这么容易了。
不过呢,一旦咱们识别出了状态,而后识别出了会根据必定的触发条件发生状态转移,那么十有八九就可使用状态模式了。
源码连接
更多内容,欢迎关注公众号: