1.定义
状态模式也是一种行为型模式,允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
随着城市的发展,现在楼房都是高楼大厦,对于楼房而言,最基础的配置那肯定是电梯了;电梯可以打开电梯门、关闭电梯门、上下运行、以及停止状态。而对应代码则是如下所示。
public interface ILift {
/**
* 电梯门开启
*/
void open();
/**
* 电梯门关闭
*/
void close();
/**
* 电梯启动
*/
void run();
/**
* 电梯停止
*/
void stop();
}
public class LiftImpl implements ILift{
@Override
public void open() {
System.out.println("电梯门开启...");
}
@Override
public void close() {
System.out.println("电梯门关闭...");
}
@Override
public void run() {
System.out.println("电梯启动...");
}
@Override
public void stop() {
System.out.println("电梯停止...");
}
}
public class Test {
public static void main(String[] args) {
ILift lift = new LiftImpl();
//打开电梯门,人员上电梯
lift.open();
//电梯门关闭
lift.close();
//电梯运行中
lift.run();
//电梯到达指定楼层停止
lift.stop();
}
}
运行结果如下,是不是非常简单,实际上对于open、close、run、stop等方法可以使用模板方法模式。
电梯门开启。。。
电梯门关闭。。。
电梯上下跑起来。。。
电梯停止了
电梯运行改良版
上面的代码非常的简单,但是存在一个问题,电梯并不是所有状态都能干其他的事情,比如电梯运行的时候不能打开门等,这是没有考虑到的点,于是将代码稍作修改变成如下所示。
public interface ILift {
//电梯的四个状态
//门敞状态
public final static int OPENING_STATE = 1;
//门闭状态
public final static int CLOSING_STATE = 2;
//运行状态
public final static int RUNNING_STATE = 3;
//停止状态
public final static int STOPPING_STATE = 4;
/**
* 设置电梯状态
* @param state
*/
void setState(int state);
/**
* 电梯门开启
*/
void open();
/**
* 电梯门关闭
*/
void close();
/**
* 电梯启动
*/
void run();
/**
* 电梯停止
*/
void stop();
}
下面电梯的实现中,规定了什么状态下能做什么事情,比如对应open方法即打开电梯门,什么状态下能打开电梯门呢?关闭和停止状态。而对于close方法来说,什么情况下能关闭电梯门呢?只有打开门状态。
public class LiftImpl implements ILift{
private int state;
@Override
public void setState(int state) {
this.state = state;
}
@Override
public void open() {
//什么状态下能打开电梯门
switch (this.state){
//如果是开启状态,那么不需要处理
case OPENING_STATE :
break;
//如果是关闭状态,那么是可以开启门的
case CLOSING_STATE :
openWithoutLogic();
setState(CLOSING_STATE);
break;
//如果是运行状态,当然这种情况下是不允许开启门的
case RUNNING_STATE :
break;
//如果是停止状态,此时当然可以开启门
case STOPPING_STATE :
openWithoutLogic();
setState(OPENING_STATE);
break;
}
}
@Override
public void close() {
//什么状态下才能关闭电梯门
switch (this.state){
//如果是开启状态,可以进行关闭电梯门
case OPENING_STATE :
setState(CLOSING_STATE);
closeWithoutLogic();
break;
//如果是关闭状态,那么不需要再关闭
case CLOSING_STATE :
break;
//如果是运行状态,运行时门本来就是关闭的
case RUNNING_STATE :
break;
//如果是停止状态,门本来就是关闭的
case STOPPING_STATE :
break;
}
}
@Override
public void run() {
//什么状态下才能电梯运行
switch (this.state){
//如果是开启状态,不允许运行
case OPENING_STATE :
break;
//如果是关闭状态,可以运行
case CLOSING_STATE :
setState(RUNNING_STATE);
runWithoutLogic();
break;
//如果是运行状态,本来就是运行不需要再更改状态
case RUNNING_STATE :
break;
//如果是停止状态,可以运行,其他人使用电梯时
case STOPPING_STATE :
setState(RUNNING_STATE);
runWithoutLogic();
break;
}
}
@Override
public void stop() {
//什么状态下才能电梯停止
switch (this.state){
//如果是开启状态,不能停止
case OPENING_STATE :
break;
//如果是关闭状态,可以停止电梯
case CLOSING_STATE :
setState(STOPPING_STATE);
stopWithoutLogic();
break;
//如果是运行状态,中间有人下电梯,肯定可以停止
case RUNNING_STATE :
setState(STOPPING_STATE);
stopWithoutLogic();
break;
//如果是停止状态,本来就是停止,不需要更改状态
case STOPPING_STATE :
break;
}
}
//单纯的电梯门打开,不考虑其他条件
public void openWithoutLogic(){
System.out.println("电梯门打开。。。");
}
//单纯的电梯门关闭,不考虑其他条件
public void closeWithoutLogic(){
System.out.println("电梯门关闭。。。");
}
//纯粹的运行,不考虑其他条件
private void runWithoutLogic(){
System.out.println("电梯上下跑起来...");
}
//单纯的停止,不考虑其他条件
private void stopWithoutLogic(){
System.out.println("电梯停止了...");
}
}
现在电梯的功能比较完善了,也支持了不能随意变化状态,运行结果如下,但是依然存在问题,如果此时要增加一个维修状态,那么所有的方法中都要增加case判断,同时还需要在对应的维修状态下增加switch进行判断,这是完全违背开闭原则的。同时随着状态的增多大量的switch和case使得代码异常的复杂。
public class Test {
public static void main(String[] args) {
ILift lift = new LiftImpl();
lift.setState(ILift.STOPPING_STATE);
//首先是电梯门开启,人进去
lift.open();
//然后电梯门关闭
lift.close();
//再然后,电梯跑起来,向上或者向下
lift.run();
//最后到达目的地,电梯挺下来
lift.stop();
}
}
电梯门打开。。。
电梯门关闭。。。
电梯上下跑起来...
电梯停止了...
2.状态模式实现
电梯运行终极版
现在知道电梯的运行代码存在的问题后,我们尝试使用状态模式进行更改。状态模式将所有的状态抽象为对应的类,然后都继承于LiftState,这样就保证了所有的状态是一致性的。同时使用Context来引用不同的状态,然后根据不同的情况进行状态的流转。这样就实现了文章开头前所说的,允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类。
LiftState用于约束不同的状态,所有的状态都继承与LiftState,保证状态之间的一致性行为。
public abstract class LiftState {
protected Context context;
public void setContext(Context context) {
this.context = context;
}
//首先电梯门开启动作
public abstract void open();
//电梯门有开启,那当然也就有关闭了
public abstract void close();
//电梯要能上能下,跑起来
public abstract void run();
//电梯还要能停下来,停不下来那就扯淡了
public abstract void stop();
}
Context用于保存不同的状态,每一个电梯状态都是一个独立的类,同时电梯在什么状态行能被其他状态给触发,也是通过Context进行流转的。
public class Context {
//定义出所有的电梯状态
public final static OpenningState openningState = new OpenningState();
public final static ClosingState closeingState = new ClosingState();
public final static RunningState runningState = new RunningState();
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.setContext(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();
}
}
OpnningState状态下,电梯门是打开,所以对于open方法来说只是打开电梯门,而在打开状态下,只能进行关闭,而停止和打开状态在OpnningState状态下本身就是存在的,所以只有关闭状态下可以发生流转,而流转是则是用过Context进行的。
public class OpenningState extends LiftState{
@Override
public void open() {
System.out.println("电梯门开启。。。");
}
@Override
public void close() {
//状态修改
super.context.setLiftState(Context.closeingState);
//动作委托为CloseState来执行
super.context.getLiftState().close();
}
@Override
public void run() {
//运行不能打开门
}
@Override
public void stop() {
//开门肯定停止了
}
}
而ClosingState状态下,电梯门是关闭,所以对于close方法来说只是关闭电梯门,而在关闭状态下,可以打开门和运行以及停止。
public class ClosingState extends LiftState{
@Override
public void open() {
//状态修改
super.context.setLiftState(Context.openningState);
//动作委托为openningState来执行
super.context.getLiftState().open();
}
@Override
public void close() {
System.out.println("电梯门关闭。。。");
}
@Override
public void run() {
//关门的状态下可以运行
//状态修改
super.context.setLiftState(Context.runningState);
//动作委托为runningState来执行
super.context.getLiftState().run();
}
@Override
public void stop() {
//状态修改,关门状态下可以停止
super.context.setLiftState(Context.stoppingState);
//动作委托为runningState来执行
super.context.getLiftState().stop();
//停止状态门本来就是关闭的
}
}
而RunningState状态下,电梯处于运行状态,由于运行状态不能打开门,只能进行停止(其他人上电梯或下电梯)。
public class RunningState extends LiftState{
@Override
public void open() {
//运行状态不能开门,什么也不做
}
@Override
public void close() {
//运行状态门本来就是关闭,什么也不做
}
@Override
public void run() {
System.out.println("电梯上下跑起来。。。");
}
@Override
public void stop() {
//停止状态门下可以运行,存在其他楼层用电梯
super.context.setLiftState(Context.stoppingState);
super.context.getLiftState().stop();
}
}
而StoppingState状态下,电梯处于停止状态,停止状态下可以开门,以及可以运行。
public class StoppingState extends LiftState{
@Override
public void open() {
//停止状态可以开门
super.context.setLiftState(Context.openningState);
super.context.getLiftState().open();
}
@Override
public void close() {
//停止状态门本来就是关闭的,不需要做什么
}
@Override
public void run() {
//停止状态可以开门
super.context.setLiftState(Context.runningState);
super.context.getLiftState().run();
}
@Override
public void stop() {
System.out.println("电梯停止了");
}
}
现在只需要通过Context来存放具体的状态类,然后进行电梯的状态,当然这4种状态实际上也可以写一个模板方法,然后进行调用对应的模板。
public class Test {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(Context.stoppingState);
context.open();
context.close();
context.run();
context.stop();
}
}
电梯门打开。。。
电梯门关闭。。。
电梯上下跑起来...
电梯停止了...
状态模式中的角色
State(状态),State角色,定义了不同的状态,例如电梯的所有状态的抽象,文中由LiftState扮演此角色
ConcreteState(具体的角色),ConcreteState角色,是对State角色的具体实现,文中由OpenningState、ClosingState、RunningState、StoppingState扮演此角色。
Context(状况、状态上下文),Context角色,是对状态进行流转,以及存放状态类的地方,文中由Context扮演此角色。
参考文献《设计模式之禅》
代码获取地址:https://gitee.com/bughong/design-pattern