状态State,指某事物所处的状况或形态,比如水的三态,零下会变成固态冰,常温会是液态水,100℃会蒸发成气态的水蒸气。
在这个地球生态系统中,水的总量并不会增加,也不会减少,只是随着温度的变化其分子间发生了稀松紧密的变化罢了,于是便有了不同的行为,比如流动、凝固、或是蒸腾,但对于其本质H2O分子对象并没有任何变化,变化的,只是其形态。
当然,事物的状态都是不同的,有的多有的少。物质基本三态,人的精神状态更是非常复杂多变的,喜怒哀乐,五味杂陈。更有趣的是,对于某些患有严重的精神分裂的病人来说,其精神状态更是变化无常,有些竟可以扮演几十种角色,随时间或境遇切换,一会变成精明聪颖的律师,一会是懦弱的失败者总是要自杀,一个境遇触发又是愤怒的杀人暴徒,这人格切换速度,丧心病狂到令人发指。
言归正传,依旧老惯例,我们还是用极简主义阴阳二态来做药引子,想必每个人家里都有开关吧,其暴露出两个UI可操作接口(对接你的手指):开,关。很简单吧?
好我们来分析一下,首先得定义一个类吧,就叫它:Switcher好了,对外暴露两个方法:switchOn()以及switchOff(),以便用户调用,OK,开始我们的代码。
1public class Switcher { 2 //false代表关,true代表开 3 private boolean state = false;//初始状态是关 4 5 public void switchOn(){ 6 state = !state; 7 System.out.println("OK...灯亮"); 8 } 9 10 public void switchOff(){ 11 state = !state; 12 System.out.println("OK...灯灭"); 13 } 14}
完成了?没问题了?这也太简单了吧?当然说这个没问题是在前端UI壳子设计精妙的前提下,但这并不能代表我们的程序设计没问题。试想如果UI可以重复调用开或者关会出现什么情况?状态乱套了!这个设计是非常不可靠的,我们不能因为表面设计上的完美就忽略了后端代码功能的逻辑正确性,表里不一。这就是为什么我们做应用时不但要做好前端校验(用户体验),更要保证后端校验(功能正确性)不可缺失。
想明白了的话我们继续,现在改一下我们之前的设计,这里一定要加入针对当前状态的条件判断,也就是说,开的状态不能再开,关的状态不能再关!
1public class Switcher { 2 //false代表关,true代表开 3 boolean state = false;//初始状态是关 4 5 public void switchOn(){ 6 if(state == false){//当前是关状态 7 state = true; 8 System.out.println("OK...灯亮"); 9 }else{//当前是开状态 10 System.out.println("WARN!!!通电状态无需再开"); 11 } 12 } 13 14 public void switchOff(){ 15 if(state == true){//当前是开状态 16 state = false; 17 System.out.println("OK...灯灭"); 18 }else{//当前是关状态 19 System.out.println("WARN!!!断电状态无需再关"); 20 } 21 } 22}
我们可以看到这里加入了逻辑判断,如果重复开或者重复关的话是会告警的,当然这里也可以抛异常出去,我们就不搞那么复杂化了。那对于这样的设计没有问题吧?很显然,逻辑上是跑的通的,写个Client类测试一下。
public class Client { public static void main(String[] args) { Switcher s = new Switcher(); s.switchOff();//WARN!!!断电状态无需再关 s.switchOn();//OK...灯亮 s.switchOff();//OK...灯灭 s.switchOn();//OK...灯亮 s.switchOn();//WARN!!!通电状态无需再开 } }
So far,不管熊孩子怎么开开关关都不会有问题了。可惜我还是要很遗憾地告诉你,这样的设计仍然是糟糕的。试想,如果状态不止一种,并且状态切换有及其复杂的逻辑,例如,之前那个精神病患者,或者汽车的自动挡。
如果按照这种设计的结果会是?码农一定要有一种打破砂锅问到底的精神,不撞南墙不回头,Lu起袖子马上干!我们写一小段代码来看看先。
1public class Car { 2 //0:Park驻车档,1:Reverse倒退挡, 3 //2:Neutral空挡,3:Drive前进档。 4 String state = "P";//初始状态是P档 5 6 public void push(){//向上推档杆 7 switch (state) { 8 case "P"://驻车档状态 9 System.out.println("WARN!!!到头了推不动了!"); 10 break; 11 case "R"://倒挡状态 12 state = "P"; 13 System.out.println("OK...切P档"); 14 break; 15 case "N"://空档状态 16 System.out.println("OK...切R档"); 17 break; 18 case "D"://前进档状态 19 System.out.println("OK...切N档"); 20 break; 21 default: 22 break; 23 } 24 } 25 26 public void pull(){//向下拉档杆 27 //这里省略,逻辑同上类似 28 } 29}
不用多说什么了吧,这个是在作死了,那一大堆逻辑判断写在宿主类里会越来越像蜘蛛网!我们必须想方设法把这个设计给模块化,把状态模块给独立出来!还记得我们曾经讲过的设计模式是什么鬼(策略)吧,算法策略被抽离出来,这里举一反三,把状态也给抽离出来,好了办法有了,我们忘掉自动挡,继续用我们大道至简的开关例子。
public interface State { public void switchOn(Switcher switcher);//开 public void switchOff(Switcher switcher);//关 }
以上我们首先了定义一个状态State接口,两个方法开与关,注意这里与策略模式不同的是,我们为了与宿主Switcher对接所以把它作为参数传入。然后是开状态与关状态的实现。
1public class On implements State { 2 @Override 3 public void switchOn(Switcher switcher) { 4 System.out.println("WARN!!!通电状态无需再开"); 5 return; 6 } 7 8 @Override 9 public void switchOff(Switcher switcher) { 10 switcher.setState(new Off()); 11 System.out.println("OK...灯灭"); 12 } 13}
1public class Off implements State { 2 @Override 3 public void switchOn(Switcher switcher) { 4 switcher.setState(new On()); 5 System.out.println("OK...灯亮"); 6 } 7 8 @Override 9 public void switchOff(Switcher switcher) { 10 System.out.println("WARN!!!断电状态无需再关"); 11 return; 12 } 13}
显而易见,注意看第10行代码,开状态不能做开行为,只告警并返回,关状态反之亦然。而第4行代码则是合法的行为,所以可以进行状态切换并实施相应行为,也就是说,开状态可关,关状态可开。注意这里是把宿主对象传入进来用于切换其当前状态,亦或是调用宿主的具体功能方法(这里省略用打印输出代替),比如宿主里的一盏灯提供的方法。
至此,一切看起来非常优雅,我们已经成功的将状态从宿主中抽离了,最后再来看宿主开关类是什么样子。
1public class Switcher { 2 //开关的初始状态设置为“关” 3 private State state = new Off(); 4 5 public State getState() { 6 return state; 7 } 8 9 public void setState(State state) { 10 this.state = state; 11 } 12 13 public void switchOn(){ 14 state.switchOn(this);//这里调用的是当前状态的开方法 15 } 16 17 public void switchOff(){ 18 state.switchOff(this);//这里调用的是当前状态的关方法 19 } 20}
甚至我们还可以给里面加一盏灯,像之前我们提到的那样,在State状态接口实现里去调用。
public class Switcher { //...之上代码略... private Lamp lamp; public void lampOn(){ lamp.on(); } public void lampOff(){ lamp.off(); } }
看明白了吧?是不是很像策略模式?其实它就是策略的一个变种,只不过状态模式会更好的根据当前的状态去实施不同的行为,并且自主切换到另一个正确的状态,开变关,关变开。就好似电梯(虽然是嵌入式面向过程,这里只是举例),用户根本无法随意强制更改其状态以及行为,你让它上,它不一定马上就能上,否则会造成事故。电梯内部封装了多个状态以及对应的逻辑产生不同的行为,它会根据当前状态去自我调整并实施最优方案,以达到安全、高效的目的,这才是可靠的设计。
这些例子都很简单吧?确实很简单,但也不简单,例子本身简单,理解并不简单,所以大家一定要多分析思考,举一反三,最终才能融汇贯通,自由运用。光说不练是不行的,理论指导实践,实践加强理论,建议大家亲自去写一下上面的汽车自动挡例子。
推荐大而全的【后端技术精选】