1. 状态模式的定义
状态模式,又称状态对象模式(Pattern of Objects for States),状态模式是对象的行为模式。
状态模式允许一个对象在其内部状态改变的时候改变其行为。这个对象看上去就像是改变了它的类一样。
举个例子:海贼王中路飞在打多弗朗明哥的时候,首先是普通状态,然后发怒开启二挡状态,被多弗朗明哥嘲笑速度快,但是力量低,于是开启三挡状态,又被嘲笑力量够了,但是速度差远了,路飞被逼急,于是开启四挡,最终打败了多弗朗明哥。现在我们通过代码实现这样的一个过程。
这里我们发现路飞的状态经历了普通,二挡,三挡,四挡,共四个状态,我们创建一个实例变量state来持有目前的状态,然后定义每个状态的值。
public final static int ORDINARY = 0;//普通状态
public final static int SECONDGEAR = 1;//二挡状态
public final static int THIRDGEAR = 2;//三挡状态
public final static int FOURTHGEAR = 3;//四挡状态
private int state = ORDINARY;//由于路飞一开始是普通状态,所以我们初始化state为ORDINARY
2. 初步实现代码
实现LuFei这个类:
public class LuFei {
public final static int ORDINARY = 0;//普通状态
public final static int SECONDGEAR = 1;//二挡状态
public final static int THIRDGEAR = 2;//三挡状态
public final static int FOURTHGEAR = 3;//四挡状态
private int state = ORDINARY;//由于路飞一开始是普通状态,所以我们初始化state为ORDINARY
public void setstate(int state) {
this.state = state;
}
public void change(){
if(state == SECONDGEAR){
System.out.println("路飞开启二挡战斗");
}else if(state == THIRDGEAR){
System.out.println("路飞开启三挡战斗");
}else if(state == FOURTHGEAR){
System.out.println("路飞开启四挡战斗");
}else{
System.out.println("路飞当前为普通状态战斗");
}
}
}
现在我们写个测试类:
public class TestLuFei {
public static void main(String[] args) {
LuFei luFei = new LuFei();
luFei.setstate(luFei.SECONDGEAR);
luFei.change();
luFei.setstate(luFei.THIRDGEAR);
luFei.change();
luFei.setstate(luFei.FOURTHGEAR);
luFei.change();
luFei.setstate(luFei.ORDINARY);
luFei.change();
}
}
打印输出结果:
路飞开启二挡战斗
路飞开启三挡战斗
路飞开启四挡战斗
路飞当前为普通状态战斗
这里可以看到,通过状态的改变,路飞会以不同的状态进行战斗。
这里,路飞在第一次变四挡后,并没有打败多弗朗明哥,而是因为四挡导致进入了第五个状态:虚弱状态,现在我们还要再加上一个if语句,把虚弱状态扔进去。这里就看出了问题,我们这个由于需求及其简单,所以代码特别简洁,但是倘若这是一个复杂的业务,我们每一次变更状态难道都要在类中增加if else语句或者是新的业务逻辑吗?这显然违背了我们的设计原则:封装变化原则。(这里各位可能会突然想起来策略模式,那里也用到了这个原则。)
3. 改进代码
现在,让我们重写代码,将每个状态对象封装到各自的类中,然后在动作发生时委托给当前对象。我们的步骤为:
-
首先,我们创建一个state的接口,我们把每个状态共有的change方法放在这个接口中。
-
接下来,我们让每一个状态都实现状态类,这些类将负责在对应的状态下进行相应行为。
-
最后,我们将动作委托到状态类中。
interface LuFeiState{
public void change();
}
class Ordinary implements LuFeiState{
@Override
public void change() {
System.out.println("路飞当前为普通状态战斗");
}
}
class SecondGear implements LuFeiState{
@Override
public void change() {
System.out.println("路飞开启三挡战斗");
}
}
class ThirdGear implements LuFeiState{
@Override
public void change() {
System.out.println("路飞开启三挡战斗");
}
}
class FourthGear implements LuFeiState{
@Override
public void change() {
System.out.println("路飞开启四挡战斗");
}
}
由于LZ的State类在其他包下已经定义过了,所以这里LZ改名为LuFeiState。
接下来我们修改路飞类:
public class LuFei {
public static final LuFeiState Ordinary = new Ordinary();//普通状态
public static final LuFeiState SecondGear = new SecondGear();//二挡状态
public static final LuFeiState ThirdGear = new ThirdGear();//三挡状态
public static final LuFeiState FourthGear = new FourthGear();//四挡状态
private LuFeiState state = Ordinary;//由于路飞一开始是普通状态,所以我们初始化state为ORDINARY
public void setstate(LuFeiState state) {
this.state = state;
}
public void change(){
state.change();
}
}
可以看到,我们原本一大堆的if else语句消失了,代替的,是非常简洁的代码。看到这里,各位一定产生了一个问题,别急,忍住,LZ稍后会作解释。
接下来是我们的测试类:
public class TestLuFei {
public static void main(String[] args) {
LuFei luFei = new LuFei();
luFei.setstate(luFei.SecondGear);
luFei.change();
luFei.setstate(luFei.ThirdGear);
luFei.change();
luFei.setstate(luFei.FourthGear);
luFei.change();
luFei.setstate(luFei.Ordinary);
luFei.change();
}
}
打印结果:
路飞开启二挡战斗
路飞开启三挡战斗
路飞开启四挡战斗
路飞当前为普通状态战斗
这是我们重做之前的代码,可以看出,当有新的状态产生时,我们只需要写一个新的状态类实现State接口,在路飞类中提供一个变量即可,其他的都不需要动。对比原本的例子,我们明显的发现:
-
每个状态的行为局部化到它自己的类中
-
将容易产生问题的if else结构去掉,使得代码的可维护性更强,不易出错。
-
使用多态代替了条件判断,这样我们代码的扩展性更强,当要增加一些状态时,会非常的容易。
-
让每一个状态对修改关闭,对扩展开放
-
状态是可以被共享的,这个在上面的例子当中有体现,看下LuFei类当中的四个static final变量就知道了,因为状态类一般是没有自己的内部状态的,所有它只是一个具有行为的对象,因此是可以被共享的。
-
状态的转换更加简单安全,简单体现在状态的分割,因为我们把一堆if else分割成了若干个代码段分别放在几个具体的状态类当中,所以转换起来当然更简单,而且每次转换的时候我们只需要关注一个固定的状态到其他状态的转换。安全体现在类型安全,我们设置状态时,必须是状态接口的实现类,而不是原本的一个整数,这可以杜绝数不正确的状态码。
写到这里,不需要明说各位也看出了我们用到的是状态模式。
状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像改变了它的类。
-
Context(上下文)是一个类,它可以拥有一些内部状态,相当于我们例子中的LuFei。
-
state.handle() :不管什么时候,只要有人调用Context的request方法,它就会被委托到状态来处理。
-
state接口定义了一个所有具体状态的共同接口;任何状态都实现这个相同的接口,这样一来状态之间可以互相替换。
-
ConcreteState(具体状态)处理来自Context的请求,每一个ConcreteState都提供了它自己对于请求的实现。所以,当Context改变状态时行为也跟着改变。
4. 状态模式适用场景
状态模式适用于某一个对象的行为取决于该对象的状态,并且该对象的状态会在运行时转换,又或者有很多的if else判断,而这些判断只是因为状态不同而不断的切换行为。
状态模式在项目当中也算是较经常会碰到的一个设计模式,但是通常情况下,我们还是在看到if else的情况下,对项目进行重构时使用,又或者你十分确定要做的项目会朝着状态模式发展,一般情况下,LZ不建议在项目的初期使用。
5. 状态模式和策略模式比较
两者的区别:
状态模式:将一群行为封装到状态对象中,context的行为随时可委托到那些状态对象中的一个。随着时间的流逝,当前状态在状态对象集合中游走改变,以反映出context内部的状态,因此,context的行为也会跟着改变。但是context的客户对于状态对象了解不多,甚至根本是浑然不觉。
而以策略模式而言,客户通常主动指定Context所要组合的策略对象是哪一个。现在,固然策略模式让我们具有弹性,能够在运行时改变策略,但对于某个context对象来说,通常都只有一个最适当的策略对象。
一般来说,我们把策略模式想成是除了继承之外的一种弹性替代方案。如果你使用继承定义了一个类的行为,你将被这个行为困住,甚至要修改它都很难。有了策略模式,你可以通过组合不同的对象来改变行为。
我们把状态模式想成是不用在context中放置许多条件判断的替代方案。通过将行为包装进状态对象中,你可以通过在context内简单地改变状态对象来改变context的行为。