状态模式以电梯为例子
电梯有哪些动作(映射到Java中就有多少方法):开门、关门、运行、停止,就这四个动作,用程序来实现一下电梯的动作,先看类图设计:
非常简单的类图,定义一个接口,然后是一个实现类,然后业务类Client就可以调用,并运行起来,先看接口:
package com.example.xpeng.statemode;
/**
* Created by xpeng on 2018/5/13.
* 定义一个电梯的接口
*/
public interface ILift {
//首先电梯门开启动作
public void open();
//电梯关闭
public void close();
//电梯跑起来
public void run();
//电梯停下来
public void stop();
}
再看实现类:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 电梯的实现类
*/
public class Lift implements ILift {
/**
* 电梯门开启
*/
@Override
public void open() {
Log.e("xyz"," 电梯门开启。。。 ");
}
/**
* 电梯门关闭
*/
@Override
public void close() {
Log.e("xyz"," 电梯门关闭。。。 ");
}
@Override
public void run() {
Log.e("xyz"," 电梯门跑起来了。。。 ");
}
@Override
public void stop() {
Log.e("xyz"," 电梯门停止。。。 ");
}
}
电梯的开关跑停都实现了,看业务是怎么调用的。
ILift lift = new Lift();
lift.open();
lift.close();
lift.run();
lift.stop();
这个太简单了,其实有很多前置条件没考虑,具体点说就是在特定状态下才能做特定事,那我们来分析一下电梯有什么特定的状态。
门敞状态 – 这个状态下电梯只能做的动作是关门动作。
门闭状态 – 这个状态下,可以进行的动作是:开门、停止、运行
运行状态 – 电梯正在跑,上下窜,这个状态下,电梯只能做的是停止
停止状态 – 电梯停止不动,在这个状态下,电梯有两个可选动作:继续运行和开门动作
用一张表表示电梯状态和动作之间的关系:
看到表以后,我们才发现,原来我们的程序做的很不严谨,好,我们来修改一下,先看类图:
在接口中分别定义了四个常量,分别表示电梯的四个状态:门敞状态、关闭状态、运行状态、停止状态,然后在实现类中电梯的每一次动作发生都要对状态进行判断,判断是否运行执行,也就是动作的执行是否符合逻辑,实现类中的四个私有方法是仅仅实现电梯的动作,没有任何的前置条件,因此这四个方法是不能为外部类调用的,设置为私有方法,我们先看接口的改变:
/**
* Created by xpeng on 2018/5/13.
* 定义一个电梯的接口
*/
public interface ILift {
//电梯的四个状态
public final static int OPENING_STATE = 1;//门敞状态
public final static int CLOSEING_STATE = 2;//门闭状态
public final static int RUNNING_STATE = 3;//运行状态
public final static int STOPPING_STATE = 4;//停止状态
//设置电梯状态
public void setState(int state);
//首先电梯门开启动作
public void open();
//电梯关闭
public void close();
//电梯跑起来
public void run();
//电梯停下来
public void stop();
}
增加了四个静态常量,增加了一个方法setState,设置电梯的状态。我们再来看看实现类是如何实现的:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 电梯的实现类
*/
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
/**
* 电梯门开启
*/
@Override
public void open() {
Log.e("xyz"," 电梯门开启。。。 ");
//电梯在什么状态下才能开启
switch (this.state){
case OPENING_STATE://如果已经在门敞开的状态,则什么也不做
break;
case CLOSEING_STATE://如果电梯是关闭状态,则可以开启
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
case RUNNING_STATE://正在运行状态,则不能开门,什么都不做
break;
case STOPPING_STATE://停止状态,当然要开门了
this.openWithoutLogic();
this.setState(OPENING_STATE);
break;
}
}
/**
* 电梯门关闭
*/
@Override
public void close() {
Log.e("xyz"," 电梯门关闭。。。 ");
//电梯在什么状态下才能关闭
switch (this.state){
case OPENING_STATE://如果是则可以关门,同时修改电梯状态
this.closeWithoutLogic();
this.setState(CLOSEING_STATE);
break;
case CLOSEING_STATE://如果电梯是关门状态,则什么都不做
break;
case RUNNING_STATE://如果是正在运行,门本来是关闭的,也说明都不做
break;
case STOPPING_STATE://如果是停止状态,门也是关闭的,什么也不做
break;
}
}
@Override
public void run() {
Log.e("xyz"," 电梯门跑起来了。。。 ");
switch (this.state){
case OPENING_STATE:
break;
case CLOSEING_STATE:
this.runWithoutLogic();
this.setState(RUNNING_STATE);
break;
case RUNNING_STATE:
break;
case STOPPING_STATE:
this.runWithoutLogic();
this.setState(RUNNING_STATE);
break;
}
}
@Override
public void stop() {
Log.e("xyz"," 电梯门停止。。。 ");
switch (this.state){
case OPENING_STATE:
break;
case CLOSEING_STATE:
this.stopWithoutLogic();
this.setState(CLOSEING_STATE);
break;
case RUNNING_STATE:
this.stopWithoutLogic();
this.setState(CLOSEING_STATE);
break;
case STOPPING_STATE:
break;
}
}
//纯粹的电梯关门,不考虑实际的逻辑
private void closeWithoutLogic(){
Log.e("xyz"," 电梯门关闭... ");
}
//纯粹的电梯开门,不考虑实际的逻辑
private void openWithoutLogic(){
Log.e("xyz"," 电梯门开启... ");
}
//纯粹的电梯运行,不考虑实际的逻辑
private void runWithoutLogic(){
Log.e("xyz"," 电梯门运行... ");
}
//纯粹的电梯停止,不考虑实际的逻辑
private void stopWithoutLogic(){
Log.e("xyz"," 电梯门停止... ");
}
}
程序有点长,但是还是很简单的,就是在每一个接口定义的方法中使用with…case来进行判断,是否运行指定的动作。我们来调用程序的变更:
ILift lift = new Lift();
//电梯初始状态应该是停止状态
lift.setState(ILift.STOPPING_STATE);
lift.open();
lift.close();
lift.run();
lift.stop();
我们来想一下,这段程序有什么问题?
首先,这个Lift.java这个文件有点长,长的原因使我们在程序中使用了大量的switch…case这样的判断,可维护性比较差
其次,扩展性非常不好,大家想想,电梯还有两个状态没有加,是什么?通电状态和断电状态,程序增加这两个方法,open()、close()、run()、stop()这四个方法还要增加判断条件,这个与开闭原则相违背了。
在其次,业务上有没有问题呢,电梯有没有可能只有运行状态没有停止状态呢?电梯故障嘛,还有电梯维修的时候,可以在stop状态下不开门,等待这些特殊情况
刚刚我们是从电梯有哪些方法以及这些方法执行的条件去分析,现在我们换个角度看问题,我们来向电梯在具有这些状态的时候,能都做什么事情,也就是说在电梯处于一个具体状态时,我们来思考这个状态是由什么动作触发而产生以及在这个状态下电梯还能做什么事情,举个例子来说,电梯是停止状态时,我们来思考两个问题:
第一、这个停止状态是怎么来的,那当然是由于电梯执行了stop方法而来的
第二、在停止状态下,电梯还能做什么动作?继续运行?开门?当然都可以咯。
我们再来分析其他三个状态,也都是一样的额结果,我们只要实现一个状态下的两个任务模型就可以了:这个状态时怎么产生的以及在这个状态下还能做什么其他动作(也就是这个状态怎么过度到其他的状态),既然我们以状态为参考模型,那我们就先来定义电梯的状态接口,思考之后我们来看类图:
在类图中,定义了一个LiftState抽象类,声明了一个受保护的类型Context变量,这个串联我们各个状态的封装类,封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法则,我的类内部情节你知道的越少越好,并且还定义了四个具体的实现类,承担的是状态的产生以及状态间转换过度,我们先看LiftStatr程序:
/**
* Created by xpeng on 2018/5/13.
* 定义一个电梯的接口
*/
public abstract class LiftState {
//定义一个环境角色,也就是封装状态的变换引起的功能变化
protected LiftState liftState;
public void setLiftState(LiftState liftState){
this.liftState = liftState;
}
public abstract void open();
public abstract void close();
public abstract void run();
public abstract void stop();
}
抽象类比较简单,我们来看一个具体的实现,门敞状态的实现类:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 在电梯门开启的状态下能做什么事情
*/
public class OpenningState extends LiftState {
@Override
public void open() {
Log.e("xyz"," 电梯门开启... ");
}
/**
* 开启当然可以关闭了,我就想测试一下电梯门开关功能
*/
@Override
public void close() {
//状态修改
super.contextState.setLiftState(ContextState.closeState);
//动作委托为CloseState来执行
super.contextState.getLiftState().close();
}
@Override
public void run() {
}
//开门还不停止???
@Override
public void stop() {
//do nothing ...
}
}
来解释一下这个类的几个方法,Openning状态是由open()方法产生的,因此这个方法中有一个具体的业务逻辑,我们使用log来代替了;在Openning状态下,电梯能过度到其他什么状态呢?按照现在的定义只能过度到Closeing状态,因此我们在Close()中定义了状态变更,同时把Close这个动作委托给了CloseState类下的Close方法执行难,这个可能不好理解,我们再看看ContextState类可能好理解一点:
package com.example.xpeng.statemode;
import android.content.Context;
/**
* Created by xpeng on 2018/5/13.
*/
public class ContextState {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();
public final static CloseingState closeState = new CloseingState();
public final static RuningState runingState = new RuningState();
public final static StoppingState stoppingState = new StoppingState();
//定一个当前电梯状态
private LiftState liftState;
public LiftState getLiftState(){
return liftState;
}
public void setLiftState(LiftState liftState){
this.liftState = liftState;
//把当前的环境通知到各个实现类
this.liftState.setContextState(this);
}
public void open(){
this.liftState.open();
}
public void close(){
this.liftState.close();
}
public void run(){
this.liftState.run();
}
public void stop(){
this.liftState.stop();
}
}
结合以上三个类,我们可以这么理解,ContextState是一个环境角色,它的作用是串联各个状态的过度,在LiftSate抽象类中我们定义了并把这个环境角色聚合进来,并传递到了子类,也就是四个具体的实现类中自己根据环境来决定如何进行状态的过度。接下来看看三个具体实现类,下面是关闭状态:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 在电梯门关闭的状态下能做什么事情
*/
public class CloseingState extends LiftState {
/**
* 电梯门关了再打开,逗你玩呢,这个必须允许啊
*/
@Override
public void open() {
super.contextState.setLiftState(ContextState.openningState);
super.contextState.getLiftState().open();
}
/**
* 电梯门关闭,还是关闭状态要实现的动作
*/
@Override
public void close() {
Log.e("xyz"," 电梯门关闭... ");
}
/**
* 电梯门关了就跑,这是再正常不过了
*/
@Override
public void run() {
super.contextState.setLiftState(ContextState.runingState);//设置为运行状态
super.contextState.getLiftState().run();
}
/**
* 电梯门关着,我就不按楼层
*/
@Override
public void stop() {
super.contextState.setLiftState(ContextState.openningState);//设置为停止状态
super.contextState.getLiftState().open();
}
}
下面是电梯的运行状态:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 在电梯运行的状态下能做什么事情
*/
public class RuningState extends LiftState {
//开启当然可以关闭了,我就想测试一下电梯门开关功能
@Override
public void open() {
}
@Override
public void close() {
}
/**
* 运行状态下实现的方法
*/
@Override
public void run() {
Log.e("xyz", " 电梯上下跑... ");
}
/**
* 这个事绝对是合理的,光运行不停止还有谁敢坐这个电梯呢?!
*/
@Override
public void stop() {
super.contextState.setLiftState(ContextState.stoppingState);//环境设置为停止状态
super.contextState.getLiftState().stop();
}
}
下面是停止状态:
package com.example.xpeng.statemode;
import android.util.Log;
/**
* Created by xpeng on 2018/5/13.
* 在电梯停止的状态下能做什么事情
*/
public class StoppingState extends LiftState {
//停止状态,开门,是可以滴
@Override
public void open() {
super.contextState.setLiftState(ContextState.openningState);
super.contextState.getLiftState().open();
}
@Override
public void close() {
}
@Override
public void run() {
super.contextState.setLiftState(ContextState.runingState);
super.contextState.getLiftState().run();
}
@Override
public void stop() {
Log.e("xyz", " 电梯停止了... ");
}
}
具体调用:
ContextState contextState = new ContextState();
contextState.setLiftState(new CloseingState());
contextState.open();
contextState.close();
contextState.run();
contextState.stop();
实现结果:
05-20 16:39:33.210 3554-3554/com.example.xpeng.statemode E/xyz: 电梯门开启...
05-20 16:39:33.210 3554-3554/com.example.xpeng.statemode E/xyz: 电梯门关闭...
05-20 16:39:33.210 3554-3554/com.example.xpeng.statemode E/xyz: 电梯上下跑...
05-20 16:39:33.210 3554-3554/com.example.xpeng.statemode E/xyz: 电梯停止了...
05-20 16:39:33.210 3554-3554/com.example.xpeng.statemode E/xyz: 电梯上下跑...
上面的例子中多次提到状态,那我们现在所学的就是状态模式,什么是状态模式呢?
当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。
怎么理解这句话:
也就是说状态模式封装的非常好,状态的变更引起了行为的变更,外部看起来好像对这个类发生了改变一样,状态模式的通用实现类如下:
状态模式有什么优点呢?
首先,避免了过多的switch…case或者if…case语句的使用,避免了程序的复杂性;
其次,很好的体现了开闭原则和单一职责原则,每个状态都是一个子类,你要增加状态就增加子类,你要修改状态,你只修改一个子类就可以了。
最后一个,封装性非常好,也就是状态模式的基本要求,状态变换放置到了类的内部实现,外部的调用不用知道类内部如何实现状态和行为的便环保。
状态模式的缺点:
只有一个缺点,子类会太多,也就是类膨胀
**