前言
在状态模式(State Pattern)中,类的行为是基于它的状态改变的。这种类型的设计模式属于行为型模式。状态模式的定义就是当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。
需求
大家都知道电梯吧,电梯可以简单提取出四个动作,分别是:开门、关门、运行、停止,但是为了安全起见,开门状态是不能运行的等情况。
基本实现
电梯抽象类ILift :
public interface ILift {
int CLOSING_STATE = 0; //关门状态
int OPENING_STATE = 1; //开门状态
int RUNNING_STATE = 2; //运行状态
int STOPING_STATE = 3; //停止状态
//设置电梯的状态
void setState(int state);
//电梯门关闭
void close();
//电梯门开启
void open();
//电梯门运行(上上下下)
void run();
//电梯门停止运行
void stop();
}
电梯类Lift :
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
@Override
public void close() {
switch (state) {
case CLOSING_STATE: //本来就是关闭状态,就无需关闭了
//do nothing
break;
case OPENING_STATE: //开启状态,当然可以关闭了
closeWithoutLogic();
this.setState(CLOSING_STATE);
break;
case RUNNING_STATE: //运行状态,本就是关闭的
//do nothing
break;
case STOPING_STATE: //停止状态,本就是关闭的
//do nothing
break;
}
}
@Override
public void open() {
switch (state) {
case CLOSING_STATE: //关闭状态,当然可以
openWithoutLogic();
setState(OPENING_STATE);
break;
case OPENING_STATE: //关闭状态,当然可以打开了
openWithoutLogic();
setState(OPENING_STATE);
break;
case RUNNING_STATE: //运行状态,当然不能打开了
//do nothing
break;
case STOPING_STATE: //停止状态,当然可以打开了
openWithoutLogic();
setState(OPENING_STATE);
break;
}
}
@Override
public void run() {
switch (state) {
case CLOSING_STATE: //关闭状态,当然可以运行了
runWithoutLogic();
setState(RUNNING_STATE);
break;
case OPENING_STATE: //开启状态,不能运行咯,否则就是事故了
//do nothing
break;
case RUNNING_STATE: //本就运行状态,无需再运行了
//do nothing
break;
case STOPING_STATE: //停止状态,可以运行
runWithoutLogic();
setState(RUNNING_STATE);
break;
}
}
@Override
public void stop() {
switch (state) {
case CLOSING_STATE: //关闭状态,当然可以停止了
stopWithoutLogic();
setState(STOPING_STATE);
break;
case OPENING_STATE: //开启状态,不能停止
//do nothing
break;
case RUNNING_STATE: //运行状态,可以停止
stopWithoutLogic();
setState(STOPING_STATE);
break;
case STOPING_STATE: //本就停止状态,无需停止了
//do nothing
break;
}
}
private void closeWithoutLogic() {
System.out.println("电梯门关闭...");
}
private void openWithoutLogic() {
System.out.println("电梯门打开...");
}
private void runWithoutLogic() {
System.out.println("电梯运行中...");
}
private void stopWithoutLogic() {
System.out.println("电梯停止中...");
}
}
Client调用端:
public class Client {
public static void main(String[] args) {
Lift lift = new Lift();
//电梯的初始条件应该是停止状态
lift.setState(Lift.STOPING_STATE);
//首先是电梯门开启,人进去
lift.open();
//随后门关闭
lift.close();
//然后电梯跑起来
lift.run();
//最后到达目的地,停下来
lift.stop();
}
}
运行,输出为:
电梯门打开...
电梯门关闭...
电梯运行中...
电梯停止中...
我们来看一下,这段程序有什么问题呢?首先这段程序的电梯类过于冗余,因为用了switch...case等判断,较长的类或方法不容易维护;其次如果又新增了两个状态,如通电状态和断电状态,那是不是这种判断又要多不少,这不符合开闭原则。发现问题了,我们该怎么去解决这个问题呢?
状态模式
刚刚我们是从电梯有哪些方法以及这些方法执行的条件去分析,现在我们换个角度来看问题,我们来想电梯在具有这些状态的时候,能够做什么事情,也就是说在电梯处于一个具体状态时,我们来思考这个状态是由什么动作触发而产生以及在这个状态下电梯还能做什么事情。
定义一个LiftState抽象类,声明了一个受保护类型的Context变量,这个是串联我们各个状态的封装类,封装的目的很明显,就是电梯对象内部状态的变化不被调用类知晓,也就是迪米特法则了,我的类内部情节你知道越少越好,并且还定义了四个具体的实现类,承担的是状态的产生以及状态间的转换过度。
LiftState抽象类:
public abstract class LiftState {
protected Context context;
public void setContext(Context context) {
this.context = context;
}
public abstract void close();
public abstract void open();
public abstract void run();
public abstract void stop();
}
关闭状态ClosingState :
public class ClosingState extends LiftState {
@Override
public void close() {
System.out.println("电梯门关闭...");
}
@Override
public void open() {
context.setLiftState(Context.openingState);
context.getLisfState().open();
}
@Override
public void run() {
context.setLiftState(Context.runningState);
context.getLisfState().run();
}
@Override
public void stop() {
context.setLiftState(Context.stopingState);
context.getLisfState().stop();
}
}
开启状态OpeningState :
public class OpeningState extends LiftState {
@Override
public void close() {
context.setLiftState(Context.closingState);
context.getLisfState().close();
}
@Override
public void open() {
System.out.println("电梯门打开...");
}
@Override
public void run() {
//do nothing
}
@Override
public void stop() {
//do nothing
}
}
运行中的状态RunningState :
public class RunningState extends LiftState {
@Override
public void close() {
//do nothing
}
@Override
public void open() {
//do nothing
}
@Override
public void run() {
System.out.println("电梯门运行中...");
}
@Override
public void stop() {
context.setLiftState(Context.stopingState);
context.getLisfState().stop();
}
}
停止的状态StopingState :
public class StopingState extends LiftState {
@Override
public void close() {
//do nothing
}
@Override
public void open() {
context.setLiftState(Context.openingState);
context.getLisfState().open();
}
@Override
public void run() {
context.setLiftState(Context.runningState);
context.getLisfState().run();
}
@Override
public void stop() {
System.out.println("电梯停止中");
}
}
Context类:
public class Context {
private LiftState lisfState;
public static final ClosingState closingState = new ClosingState();
public static final OpeningState openingState = new OpeningState();
public static final RunningState runningState = new RunningState();
public static final StopingState stopingState = new StopingState();
public void setLiftState(LiftState liftState) {
this.lisfState = liftState;
this.lisfState.setContext(this);
}
public LiftState getLisfState() {
return lisfState;
}
public void close() {
this.lisfState.close();
}
public void open() {
this.lisfState.open();
}
public void run() {
this.lisfState.run();
}
public void stop() {
this.lisfState.stop();
}
}
客户端Client调用:
public class Client {
public static void main(String[] args) {
Context context = new Context();
context.setLiftState(new StopingState());
context.open();
context.close();
context.run();
context.stop();
}
}
运行,输出为:
电梯门打开...
电梯门关闭...
电梯门运行中...
电梯停止中
我们来回顾一下这里的代码,我们开始说人家代码太长,我们解决了,通过各个子类来实现,每个子类的代码都很短,而且也取消了switch...case条件的判断;其次,说人家不符合开闭原则,那如果在我们这个例子中增加两个状态怎么加?增加两个子类,一个是通电状态,一个是断电状态,同时修改其他实现类的相应方法,因为状态要过渡呀,那当然要修改原有的类,只是在原有类中的方法上增加,而不去做修改;再其次,我们说人家不符合迪米特法则,我们现在呢是各个状态是单独的一个类,只有与这个状态的有关的因素修改了这个类才修改,符合迪米特法则,非常完美!
这就是状态模式,那什么是状态模式呢?当一个对象内在状态改变时允许其改变行为,这个对象看起来像是改变了其类。
总结
状态模式避免了过多的 switch-case 或 if-else语句的使用,避免了程序的复杂性与不可维护性;其次是很好的体现了开闭原则和单一职责原则,每个状态都是一个子类;最后一个好处就是封装性非常好,这也是状态模式的基本要求,状态变换放置到了类的内部来实现,外部的调用不用知道类内部如何实现状态和行为的变换。状态模式的缺点就是会导致子类会太多,也就是类膨胀,你想一个事物有十来个状态页不稀奇,如果完全使用状态模式就会有太多的子类,不好管理。
喜欢本篇博客的简友们,就请来一波点赞,您的每一次关注,将成为我前进的动力,谢谢!作者:zhang_pan