在讲述这个模式之前,我们先看一个案例:程序员加班
public class Work { private int hour = 0; //钟点 private boolean workFinished = false; //任务完成标记 public void writeProgram(){ if(hour<12){ System.out.println("当前时间:"+hour+"点 上午工作,精神百倍"); }else if(hour<13){ System.out.println("当前时间:"+hour+"点 饿了,午饭;犯困,午休"); }else if(hour<17){ System.out.println("当前时间:"+hour+"点 下午状态还不错,继续努力"); }else{ if(workFinished){ System.out.println("当前时间:"+hour+"点 下班回家了"); }else{ if(hour<21){ System.out.println("当前时间:"+hour+"点 加班哦,劳累之急"); }else{ System.out.println("当前时间:"+hour+"点 不行了,睡着了"); } } } } //省略getter、setter方法}//测试方法public class Test { public static void main(String[] args) { Work work = new Work(); work.setHour(9); work.writeProgram(); work.setHour(10); work.writeProgram(); work.setHour(12); work.writeProgram(); work.setHour(13); work.writeProgram(); work.setHour(14); work.writeProgram(); work.setHour(17); work.setWorkFinished(false); work.writeProgram(); work.setHour(19); work.writeProgram(); work.setHour(22); work.writeProgram(); }}
输出结果:
当前时间:9点 上午工作,精神百倍
当前时间:10点 上午工作,精神百倍当前时间:12点 饿了,午饭;犯困,午休当前时间:13点 下午状态还不错,继续努力当前时间:14点 下午状态还不错,继续努力当前时间:17点 加班哦,劳累之急当前时间:19点 加班哦,劳累之急当前时间:22点 不行了,睡着了若是“任务完成”,则17点、19点、22点都是“下班回家了”的状态
现在,老板觉得加班到22点很长,规定员工必须在20点之前离开公司,怎么修改?
正常的程序员都会想到,改变17点后的判断逻辑,让员工在20点的时候强制离开公司。
这么做固然没错,但这样writeProgram()的责任过于重大,里面涉及太多的分支,无论是任何状态,都需要通过它来改变,这实际上是很糟糕的。面向对象设计其实就是希望做到代码的责任分解,这个类违背了“单一职责原则”,而且对整个方法的改动,维护出错的风险增加,也违背了“开放-封闭原则”。
怎样优化呢?下面介绍状态模式:
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式主要解决的是当控制一个对象状态转换的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化。当然,如果这个状态判断很简单,那就没必要用“状态模式”了。
什么意思呢?这段话有点难懂,没关系,我们用状态模式改写这个例子,通过具体的例子来说明。
//定义一个抽象状态接口,包含抽象方法“写程序”public interface State { void writeProgram(Work w);} //上午工作状态类public class ForenoonState implements State { public void writeProgram(Work w) { if(w.getHour()<12){ System.out.println("当前时间:"+w.getHour()+"点 上午工作,精神百倍"); }else{ //超过12点,则转入中午工作状态 w.setState(new NoonState()); w.writeProgram(); } }} //中午工作状态类public class NoonState implements State { public void writeProgram(Work w) { if(w.getHour()<13){ System.out.println("当前时间:"+w.getHour()+"点 饿了,午饭;犯困,午休"); }else{ //超过13点,则转入下午工作状态 w.setState(new AfternoonState()); w.writeProgram(); } }} //下午工作状态类public class AfternoonState implements State { public void writeProgram(Work w) { if(w.getHour()<17){ System.out.println("当前时间:"+w.getHour()+"点 下午状态还不错,继续努力"); }else{ //超过17点,则转入傍晚工作状态 w.setState(new EveningState()); w.writeProgram(); } }} //晚间工作状态public class EveningState implements State { public void writeProgram(Work w) { if(w.getWorkFinished()){ //如果完成任务,则转入下班状态 w.setState(new ResetState()); }else{ if(w.getHour()<21){ System.out.println("当前时间:"+w.getHour()+"点 加班哦,劳累之急"); }else{ //超过21点,则转入睡眠工作状态 w.setState(new SleepingState()); w.writeProgram(); } } }} //睡眠状态public class SleepingState implements State{ public void writeProgram(Work w) { System.out.println("当前时间:"+w.getHour()+"点 不行了,睡着了"); }} //下班休息状态public class ResetState implements State{ public void writeProgram(Work w) { System.out.println("当前时间:"+w.getHour()+"点 下班回家了"); }} //工作类,此时没有了过长的分支判断语句public class Work { private int hour = 0; //钟点 private boolean workFinished = false; //任务完成标记 private State state; //工作初始化为上午工作状态 public Work(){ state = new ForenoonState(); } public void writeProgram(){ state.writeProgram(this); } //省略getter、setter方法}
要明白我们改写案例的目的,最初的例子判断语句过长,改写的目的是为了将判断语句分成几部分,让各个部分承担各自的判断责任。
状态模式的好处是将特定状态相关的行为局部化,并且将不同状态的行为分割开来,也就是将特定的状态相关的行为都放入一个对象中,由于所有与状态相关的代码都存在于某个ConcreteState中,所以通过定义新的子类可以很容易地增加新的状态和转换。说白了,这样做的目的就是为了消除庞大的条件分支语句,大的分支判断会使得它们难以修改和扩展。状态模式通过把各种状态转移逻辑分布到State的子类之间,来减少相互间的依赖。
什么时候应该考虑使用状态模式呢?
当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时,就可以考虑使用状态模式了。另外如果业务需求某项业务有多个状态,通常都是一些枚举常量,状态的变化都是依靠大量的多分支判断语句来实现,此时应该考虑将每一种业务状态定义为一个State的子类,这样这些对象就可以不依赖于其他对象而独立变化了,某一天客户要更改需求,增加或减少业务状态或改变状态流程,这都是不困难的事。