设计模式系列-状态模式
由于最近热播清宫剧《甄嬛传》的影响(我确实是口味有点太重了),勾起了我重游故宫的欲望,想去看看过去皇上与后宫妃子们生活的地方,于是今天便将想法赋予了行动,虽然天气严寒但是也不能阻挡我这颗赤热的心。
一、应用场景
早上八点半 起床梳洗出门前往天安门,到了天安门第一感觉就是这么冷的天人怎么还是这么多?看来大家都跟我一样有一颗炽热的心啊。进入到午门让我猛然想起了一句台词->"推出午门斩首",顿时感觉伴君如伴虎啊。买票进入后我的参观路线为:太和殿也称“金銮殿”-->乾清宫-->御花园-->东六宫-->九龙壁。在参观每一处宫殿时我的心里状态都在发生着不同的变化。比如:
1、太和殿:是清明两代举行大型典礼的地方,例如像皇帝的登基、大婚、册立皇后、派将出征、公布皇榜等重大的仪式都在这里举行,让我感觉皇上的家的客厅真的宏伟气派啊,脑海中幻想着当时举行大型仪式的情景甚是激动。
2、乾清宫:该宫殿在明朝到清康熙年间,一直是皇帝居住并处理政务的寝宫,雍正即位后,将寝宫移至养心殿,这里就成了举行内廷典礼和引见官员的地方,这时我的心里感觉是不是因为养心殿距离后宫很近雍正才搬过去的呀?
3、御花园:始皇帝、皇后茶余饭后休息游乐的场所,不过御花园真的没有想象中的漂亮啊,很是失落。
4、东六宫:一般都是后宫嫔妃们的住所,不过现在大部分宫殿都成展馆了,已经看不出当年嫔妃们生活的环境了,失落失落失落。
5、九龙壁:该墙壁上有九条龙,是故宫一处著名的景观,据说当年在烧制这座九龙壁的时候,由于工艺要求极高,烧制难度极大,废品率很高。工匠们一不小心,把这条白龙龙腹烧坏了,但当时已经没有足够的时间再烧一次了,显然,大家的杀身之祸即将来临。有位木匠师傅冒着犯有欺君之罪的生命危险,连夜用木料雕刻成那块龙腹,钉补上去,刷上白色油漆,使之同原来的白龙腹颜色相同,终于瞒过了前来检查的官员,并使工匠们免去一场灾难。顿时体会到了三十六计的重要性。
不是要写设计模式吗?结果一开始搞了一大堆废话,呵呵,那么用代码来实现以下今天游故宫的场景吧。
第一步:首先能我参观了五处景点,先将五处景点定义一个枚举类型,代码如下:
1 /// <summary> 2 /// 故宫景点 3 /// </summary> 4 enum ImperialPalaceEnum 5 { 6 太和殿, 7 乾清宫, 8 御花园, 9 东六宫, 10 九龙壁 11 }
第二步:实现我每参观一处景点心里状态的变化,代码如下:
1 /// <summary> 2 /// 故宫参观类 3 /// </summary> 4 public class IPVisit 5 { 6 /// <summary> 7 /// 当前地点 8 /// </summary> 9 private ImperialPalaceEnum ipCurrentSpot; 10 public ImperialPalaceEnum IpCurrentSpot 11 { 12 set 13 { 14 ipCurrentSpot = value; 15 } 16 } 17 18 /// <summary> 19 /// 体会感受景点 20 /// </summary> 21 public void ExperienceSpot() 22 { 23 //体会感受当前景点 24 switch (ipCurrentSpot) 25 { 26 case ImperialPalaceEnum.太和殿: 27 Console.WriteLine("太和殿:皇上的家的客厅真的宏伟气派啊,脑海中幻想着当时举行大型仪式的情景甚是激动!"); 28 break; 29 case ImperialPalaceEnum.乾清宫: 30 Console.WriteLine("乾清宫:是不是因为养心殿距离后宫很近雍正才搬过去的呀?"); 31 break; 32 case ImperialPalaceEnum.御花园: 33 Console.WriteLine("御花园:御花园真的没有想象中的漂亮啊,很是失落."); 34 break; 35 case ImperialPalaceEnum.东六宫: 36 Console.WriteLine("东六宫:已经看不出当年嫔妃们生活的环境了,成展馆了."); 37 break; 38 case ImperialPalaceEnum.九龙壁: 39 Console.WriteLine("九龙壁:在宫中混,不会点三十六计都没脸见人."); 40 break; 41 } 42 } 43 }
第三步:主函数调用代码如下:
1 IPVisit visit = new IPVisit(); 2 3 visit.IpCurrentSpot = ImperialPalaceEnum.太和殿; 4 visit.ExperienceSpot(); 5 6 visit.IpCurrentSpot = ImperialPalaceEnum.乾清宫; 7 visit.ExperienceSpot(); 8 9 visit.IpCurrentSpot = ImperialPalaceEnum.御花园; 10 visit.ExperienceSpot(); 11 12 visit.IpCurrentSpot = ImperialPalaceEnum.东六宫; 13 visit.ExperienceSpot(); 14 15 visit.IpCurrentSpot = ImperialPalaceEnum.九龙壁; 16 visit.ExperienceSpot();
运行结果为:
1 太和殿:皇上的家的客厅真的宏伟气派啊,脑海中幻想着当时举行大型仪式的情景甚是激动! 2 乾清宫:是不是因为养心殿距离后宫很近雍正才搬过去的呀? 3 御花园:御花园真的没有想象中的漂亮啊,很是失落. 4 东六宫:已经看不出当年嫔妃们生活的环境了,成展馆了. 5 九龙壁:在宫中混,不会点三十六计都没脸见人. 6 请按任意键继续. . .
二、状态模式
分析上边的代码我们会发现,参观故宫IPVisit的对象的行为取决于它的状态,例如:ipCurrentEnum属性等于不同的属性值时,体会景点方法:ExperienceSpot所做的行为就会不同。这样一来首先就会导致我们一个方法出现了不同的职责,如果一个这个方法的责任过多那么维护起来必然是头疼的,例如看上边场景代码中的第二步中的代码,switch中的每一个case与break之间都实现了一种状态的行为。那么这个时候如果新增了一个景点(状态)就要修改此处的代码,导致违背了"开放-封闭原则"。这时有人就会说了,我们有的时候需求没有那么复杂我觉得用switch这样实现就很好,既简单又明了,这点我也非常赞同,设计就是根据实际需求来订的量身定做的设计才是最好的设计。话说的有点远了,那么如何优化我们上面的代码,使它可以灵活的扩展状态降低复杂度呢?不必担心,设计模式四人帮的前辈们已经在这类问题上给出了一个完美的解决方案,状态模式隆重登场!
1、什么是状态模式?
设计模式一书中的定义为:当一个对象在其内部状态改变时改变它的行为,对象看起来好像是修改其类。
2、状态模式类图
Context:维护一个状态的实例,这个实例定义的是当前的状态。
State: 该接口抽象Context一个特定状态的相关行为。
ConcreteState:实现了Context具体状态的行为。
3、状态模式代码实现
Context类用来维护一个当前状态的实例,表示当前状态,代码如下:
1 /// <summary> 2 /// 维护了当前状态的主体 3 /// </summary> 4 public class Context 5 { 6 public Context() { } 7 public Context(State state) 8 { 9 this.currentState = state; 10 } 11 private State currentState; 12 public State CurrentState 13 { 14 set 15 { 16 currentState = value; 17 } 18 } 19 20 /// <summary> 21 /// 执行当前状态 22 /// </summary> 23 public void Request() 24 { 25 currentState.Handle(this); 26 } 27 }
State接口:Context所需状态行为的抽象。代码如下:
public interface State { void Handle(Context context); }
ConcreteState类:根据State的抽象实现了Context所需状态行为的实例,代码如下:
/// <summary> /// 执行该状态对应的行为,设置下一个状态实例 /// </summary> public class ConcereteStateA : State { public void Handle(Context context) { Console.WriteLine("状态A:处理行为执行了.....切换下一个状态B......"); context.CurrentState = new ConcereteStateB(); } } /// <summary> /// 执行该状态对应的行为,设置下一个状态实例 /// </summary> public class ConcereteStateB : State { public void Handle(Context context) { Console.WriteLine("状态B:处理行为执行了.....切换下一个状态A......"); context.CurrentState = new ConcereteStateA(); } }
主函数调用如下:
Context context = new Context(new ConcereteStateA()); context.Request(); context.Request();
输出结果如下:
状态A:处理行为执行了.....切换下一个状态B......
状态B:处理行为执行了.....切换下一个状态A......
4、状态模式的优点
将特定状态的行为分开来,因为状态模式将所有状态的行为都放入到具体对象中,通过实现状态的抽象(接口State),可以很容易增加新的状态。
三、故宫游的状态模式实现
首先呢,我在参观(Visit)的时候,会参观不同的景点,体会每个景点时我内心状态都会发生变化,也就是说我每参观一个景点都会有一个对应的状态。此时每个景点就应该对应一个具体的状态实现。所以我在参观不同景点的时候当前状态实例也需要跟着切换,类图如下:
首先实现参观故宫类,每参观一个景点时都会改变我内心的状态,所以此时需要有一个属性代表当前状态,当体会该景点的时候就会执行对应的状态。代码如下:
1 /// <summary> 2 /// 参观故宫类 3 /// </summary> 4 public class IPVisitContext 5 { 6 public IPVisitContext() { } 7 public IPVisitContext(ISpotState state) 8 { 9 this.currentState = state; 10 } 11 12 /// <summary> 13 /// 当前状态 14 /// </summary> 15 private ISpotState currentState; 16 public ISpotState CurrentState 17 { 18 set 19 { 20 currentState = value; 21 } 22 } 23 24 /// <summary> 25 /// 参观景点 执行对应状态 26 /// </summary> 27 public void ExperienceSpot() 28 { 29 currentState.ExperienceSpot(this); 30 } 31 }
对于每个景点都会有不同的状态实例,此时需要一个状态的抽象,用户将来容易扩展新的状态实例,代码如下:
1 /// <summary> 2 /// 参观景点状态的抽象 3 /// </summary> 4 public interface ISpotState 5 { 6 void ExperienceSpot(IPVisitContext context); 7 }
接下来就是每个景点对应的状态实现了,代码如下:
1 /// <summary> 2 /// 太和殿状态行为实例 3 /// </summary> 4 public class 太和殿State : ISpotState 5 { 6 public void ExperienceSpot(IPVisitContext context) 7 { 8 Console.WriteLine("太和殿: 皇上的家的客厅真的宏伟气派啊,脑海中幻想着当时举行大型仪式的情景甚是激动!"); 9 context.CurrentState = new 乾清宫State(); 10 } 11 } 12 /// <summary> 13 /// 乾清宫状态行为实例 14 /// </summary> 15 public class 乾清宫State : ISpotState 16 { 17 public void ExperienceSpot(IPVisitContext context) 18 { 19 Console.WriteLine("乾清宫:是不是因为养心殿距离后宫很近雍正才搬过去的呀?"); 20 context.CurrentState = new 御花园State(); 21 } 22 } 23 /// <summary> 24 /// 御花园状态行为实例 25 /// </summary> 26 public class 御花园State : ISpotState 27 { 28 public void ExperienceSpot(IPVisitContext context) 29 { 30 Console.WriteLine("御花园:御花园真的没有想象中的漂亮啊,很是失落."); 31 context.CurrentState = new 东六宫State(); 32 } 33 } 34 /// <summary> 35 /// 东六宫状态行为实例 36 /// </summary> 37 public class 东六宫State : ISpotState 38 { 39 public void ExperienceSpot(IPVisitContext context) 40 { 41 Console.WriteLine("东六宫:已经看不出当年嫔妃们生活的环境了,成展馆了."); 42 context.CurrentState = new 九龙壁State(); 43 } 44 } 45 /// <summary> 46 /// 九龙壁状态行为实例 47 /// </summary> 48 public class 九龙壁State : ISpotState 49 { 50 public void ExperienceSpot(IPVisitContext context) 51 { 52 Console.WriteLine("九龙壁:在宫中混,不会点三十六计都没脸见人."); 53 } 54 }
主函数调用如下:
1 IPVisitContext context = new IPVisitContext(new 太和殿State());//从太和殿开始参观 2 context.ExperienceSpot(); 3 context.ExperienceSpot(); 4 context.ExperienceSpot(); 5 context.ExperienceSpot(); 6 context.ExperienceSpot();
运行结果如下:
1 太和殿:皇上的家的客厅真的宏伟气派啊,脑海中幻想着当时举行大型仪式的情景甚是激动! 2 乾清宫:是不是因为养心殿距离后宫很近雍正才搬过去的呀? 3 御花园:御花园真的没有想象中的漂亮啊,很是失落. 4 东六宫:已经看不出当年嫔妃们生活的环境了,成展馆了. 5 九龙壁:在宫中混,不会点三十六计都没脸见人. 6 请按任意键继续. . .
那么说了半天状态模式的好处了,那么怎么样来灵活的扩展状态呢?例如:我们现在需要加入一个景点的参观神武门也就是出宫的出口,我们只需如下几步即可:
第一步:新增一个神武门状态的类,代码如下:
/// <summary> /// 神武门状态行为实例 /// </summary> public class 神武门State : ISpotState { public void ExperienceSpot(IPVisitContext context) { Console.WriteLine("神武门:终于逛完故宫很是疲惫啊."); } }
第二步:将九龙壁的下一个状态挂接到神武门状态,代码如下:
1 /// <summary> 2 /// 九龙壁状态行为实例 3 /// </summary> 4 public class 九龙壁State : ISpotState 5 { 6 public void ExperienceSpot(IPVisitContext context) 7 { 8 Console.WriteLine("九龙壁:在宫中混,不会点三十六计都没脸见人."); 9 context.CurrentState = new 神武门State(); 10 } 11 }
OK 状态模式大功告成,也不枉我大冷天跑一趟故宫带回来的灵感。希望大家支持一下我给个推荐!感谢感谢!