State模式
State模式也叫状态模式,在State模式中,类的行为是基于它的状态改变的,所以State模式是一种行为型设计模式。
定义:State模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。
State模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况,把状态的判断逻辑转译到表现不同状态的一系列类当中,可以把复杂的判断逻辑简化。
下面先来看一个实例,用来描述一个人在一天各个时间段的状态(即在几点做什么事)
不使用State模式的代码:
MainClass:
public class MainClass {
public static void main(String[] args) {
Person person = new Person();
person.setHour(7);
person.doSomething();
person.setHour(12);
person.doSomething();
person.setHour(18);
person.doSomething();
person.setHour(20);
person.doSomething();
}
}
Person类:
//这是一个常规的不采用State模式的Person类
//利用大量的条件判断来进行状态的选择
public class Person {
private int hour;
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = hour;
}
//可以看到这种框架的一个缺点:
//即控制Person对象状态的条件表达式过于复杂,在doSomething中做了太多的事情
public void doSomething() {
if( hour==7 ) {
System.out.println("吃早餐");
}else if( hour==12 ) {
System.out.println("吃午饭");
}else if( hour==18 ) {
System.out.println("吃晚饭");
}else {
System.out.println("学习");
}
}
}
很显然上述例子的一个问题是条件判断逻辑过于复杂,那么再来让我们看一下使用了State模式时候的代码:
首先是State的抽象类:
package com.yyj.ex3;
public abstract class State {
public abstract void doSomething( Person person );
}
其次是充当用户对象(Context)的Person类:
package com.yyj.ex3;
//此处Person类模拟的是Context
public class Person {
private int hour;
//用户对象(Context)中最关键的是包含一个State字段,以标志当前状态
private State state;
public Person() {
state = new State_1();
}
public int getHour() {
return hour;
}
public void setHour(int hour) {
this.hour = hour;
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void doSomething() {
state.doSomething( this );
//复位(当所有方法完成以后再执行)
state = new State_1();
}
}
而后是表示各个状态的状态类:
package com.yyj.ex3;
public class State_1 extends State{
public void doSomething( Person person ) {
if( person.getHour() == 7 ) {
System.out.println("吃早饭");
} else {
person.setState( new State_2() );
person.doSomething();
}
}
}
package com.yyj.ex3;
public class State_2 extends State{
public void doSomething( Person person ) {
if( person.getHour() == 12 ) {
System.out.println("吃午饭");
} else {
person.setState( new State_3() );
person.doSomething();
}
}
}
package com.yyj.ex3;
public class State_3 extends State{
public void doSomething( Person person ) {
if( person.getHour() == 18 ) {
System.out.println("吃晚饭");
} else {
person.setState( new State_4() );
person.doSomething();
}
}
}
package com.yyj.ex3;
public class State_4 extends State {
public void doSomething( Person person ) {
System.out.println("学习"); }
}
最后写一个Main函数看一下运行结果:
package com.yyj.ex3;
public class MainClass {
public static void main(String[] args) {
Person person = new Person();
for( int i=7; i<24; i++ ) {
person.setHour(i);
person.doSomething();
}
}
}
运行结果:
State模式的结构:
既然使用类来表示状态,那么有一个很关键的点就是各个具体状态(ConcreteState)之间需要能够互相迁移,也叫做状态迁移。
另外由于一个具体对象(Context类)的状态能够看成是它的一个属性,所以不难理解Context类和抽象类之间应当是聚合关系,即Context类应当持有一个State字段,而这个State字段能够通过继承在各个不同的ConcreteState之间迁移,以表示一个具体对象包含不同的具体状态这一现实意义。
了解了这两点,让我们来重新看一下上述使用了State模式的代码:
首先本人认为(好吧本人也认为)使用State模式很重要的一点就是需要了解是谁来管理状态的迁移,所以在实例中的Person类里我加了一个hour字段来作为判断状态是否迁移的条件,而后根据hour数值的不同来设置不同的ConcreteState。
而Person类中的doSomething方法其实是调用不同ConcreteState下的doSomething方法。同时状态的迁移是在每个不同ConcreteState下的doSomething方法中实现的。(当然也可以在State中专门设置一个控制迁移方法,在doSomething方法调用之前,先调用控制状态迁移的方法,以保证Person类的doSomething方法能够调用到符合它当前所属状态的State类的doSomething方法)
接下来来看状态是如何迁移的:首先每当生成一个Person类实例,state字段会初始化为State_1,而后当调用doSomething方法时,首先会判断该Person类是否对应该状态类,如果与之相对应,则执行相应操作,而如果不对应,则通过setState方法将State_1转为State_2,以此类推,直至最后一个可能的状态State_4。
由于State_4状态之前的各个状态已经判断过,所以当前Person类所对应的状态必然是State_4,因此并不需要进行状态的判断,可以直接执行操作。
当然这还没有完,如果代码写到这便结束了会导致一个问题,就是当状态进行第一轮迁移时,能过顺利运行,但是结果是导致state始终等于State_4,而无法展开下一轮的循环,所以需要在每次Person.doSomething之后进行一次state的重置,以保证状态迁移能够顺利进行。
理解了State模式的工作原理,再来看一下State模式的优缺点:
首先是优点:
- 封装了转换规则;
- 枚举可能的状态,在枚举状态之前需要确定状态种类;
- 将所有与某个状态有关的行为放到一个类中,并且可以方便地增加新的状态,只需要改变对象状态即可改变对象的行为;
- 允许状态转换逻辑与状态对象合成一体,而不是某一个巨大的条件语句块;
- 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
其次是缺点:
- 状态模式的使用必然会增加系统类和对象的个数
- 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱
- 状态模式对"开放-封闭原则"的支持并不太好,对于可以切换状态的状态模式(如本例),增加新的状态类需要修改那些负责状态转换的源代码,否则无法切换到新增状态,而且修改某个状态类的行为也需修改对应类的源代码(对应的源代码即其他连带的具体状态类)。
小结:State模式并不难,每一个状态都定义一个相应的类,用类表示系统的状态,以此来避开复杂的条件逻辑。其精髓是定义接口,声明抽象方法,而后定义多个状态类,以实现具体方法。最后是要搞清楚每个状态类之间的状态迁移,以及每次使用状态中的具体方法后需要在Context类中把state字段重置。