前言
建议在阅读本文前先阅读策略模式这篇文章,虽说状态模式和策略模式的结构几乎相同,但它们的目的、本质完全不同。状态模式的行为是平行的、不可替换的,而策略模式的行为是彼此独立,可以相互替换。读完本章你会有更深刻的认识。
状态模式的定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
状态模式使用场景
- 行为随状态改变而改变的场景
这也是状态模式的根本出发点,例如权限设计,人员级别不同即使执行相同的行为结果也会不同,在这种情况下需要考虑使用状态模式 - 代码中包含大量与对象状态有关的条件语句。
状态模式将每一个条件分支(也就是一个状态)放入一个独立的类中,这使得可以根据对象自身的状态而做出相应的行为,从而去除了过多的、重复的 if-else 分支语句。
状态模式的 UML 类图
![](https://img.haomeiwen.com/i2949305/4e68b8eaaffd5cf5.png)
角色介绍:
- State:抽象状态类或者状态接口,表示状态下的行为
- ConcreteStateA、ConcreteStateB:具体状态类,实现了抽象状态类接口,定义本状态下的接口行为。
- Context:环境类角色,定义客户端感兴趣的接口,负责具体状态的切换。
状态模式示例
使用了状态模式
抽象状态角色
定义了状态的动作
public interface LiftState {
public void open();
public void close();
public void run();
public void stop();
}
Context 环境类
public class LiftContext {
// 4 种状态
public static final OpenningState OPENNING_STATE = new OpenningState();
public static final ClosingState CLOSEING_STATE = new ClosingState();
public static final RunningState RUNNING_STATE = new RunningState();
public static final StoppingState STOPPING_STATE = new StoppingState();
// 当前状态
private LiftState mLiftState = STOPPING_STATE;
private static final LiftContext CONTEXT = new LiftContext();
public static LiftContext getContext() {
return CONTEXT;
}
// 切换状态
public void setLiftState(LiftState liftState) {
this.mLiftState = liftState;
}
public void open() {
mLiftState.open();
}
public void close() {
mLiftState.close();
}
public void run() {
mLiftState.run();
}
public void stop() {
mLiftState.stop();
}
}
具体状态角色
开门状态
public class OpenningState implements LiftState {
@Override
public void open() {
System.out.println("电梯门开启...");
}
@Override
public void close() {
LiftContext.getContext().setLiftState(LiftContext.CLOSEING_STATE);
LiftContext.getContext().close();
}
// 门开着,无法运行
@Override
public void run() {
// do nothing
}
// 门开着,已经停止
@Override
public void stop() {
// do nothing
}
}
关门状态
public class ClosingState implements LiftState {
@Override
public void open() {
LiftContext.getContext().setLiftState(LiftContext.OPENNING_STATE);
LiftContext.getContext().open();
}
@Override
public void close() {
System.out.println("电梯门关闭...");
}
@Override
public void run() {
LiftContext.getContext().setLiftState(LiftContext.RUNNING_STATE);
LiftContext.getContext().run();
}
@Override
public void stop() {
LiftContext.getContext().setLiftState(LiftContext.STOPPING_STATE);
LiftContext.getContext().stop();
}
}
运行状态
public class RunningState implements LiftState {
// 正在运行,不能开门
@Override
public void open() {
// do nothing
}
// 正在运行,门已经关
@Override
public void close() {
// do nothing
}
@Override
public void run() {
System.out.println("电梯运行中...");
}
@Override
public void stop() {
LiftContext.getContext().setLiftState(LiftContext.STOPPING_STATE);
LiftContext.getContext().stop();
}
}
停止状态
public class StoppingState implements LiftState {
@Override
public void open() {
LiftContext.getContext().setLiftState(LiftContext.OPENNING_STATE);
LiftContext.getContext().open();
}
// 门就关着
@Override
public void close() {
// do nothing
}
@Override
public void run() {
LiftContext.getContext().setLiftState(LiftContext.RUNNING_STATE);
LiftContext.getContext().run();
}
@Override
public void stop() {
System.out.println("电梯停止了...");
}
}
客户端
public class Client {
public static void main(String[] args) {
LiftContext liftContext = LiftContext.getContext();
liftContext.setLiftState(LiftContext.STOPPING_STATE);
liftContext.open();
liftContext.close();
liftContext.run();
// 1. 运行情况下按开门按钮
liftContext.open();
liftContext.stop();
}
}
运行结果:
电梯门开启...
电梯门关闭...
电梯运行中...
电梯停止了...
假如不使用状态模式,就通过 if-else 或者 switch-case 完成,代码是下面这个样子:
定义电梯运行接口:
public interface ILift {
//电梯的4个状态
public final static int OPENNING_STATE = 1;
public final static int CLOSING_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();
}
具体电梯运行类:
public class Lift implements ILift {
private int state;
@Override
public void setState(int state) {
this.state = state;
}
@Override
public void open() {
switch (state) {
case OPENNING_STATE:
break;
case CLOSING_STATE:
openWithoutLogic();
setState(OPENNING_STATE);
break;
case RUNNING_STATE:
break;
case STOPPING_STATE:
openWithoutLogic();
setState(OPENNING_STATE);
break;
}
}
@Override
public void close() {
switch (state) {
case OPENNING_STATE:
closeWithoutLogic();
setState(CLOSING_STATE);
break;
case CLOSING_STATE:
break;
case RUNNING_STATE:
break;
case STOPPING_STATE:
break;
}
}
@Override
public void run() {
switch (state) {
case OPENNING_STATE:
break;
case CLOSING_STATE:
runWithoutLogic();
setState(CLOSING_STATE);
break;
case RUNNING_STATE:
break;
case STOPPING_STATE:
runWithoutLogic();
setState(CLOSING_STATE);
break;
}
}
@Override
public void stop() {
switch (state) {
case OPENNING_STATE:
break;
case CLOSING_STATE:
stopWithoutLogic();
setState(STOPPING_STATE);
break;
case RUNNING_STATE:
stopWithoutLogic();
setState(STOPPING_STATE);
break;
case STOPPING_STATE:
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("电梯停止了...");
}
}
电梯类很明显存在很多问题:
- 电梯实现类 Lift 过长:主要由于 swtich-case 的判断,如果再增加几种状态,该类会更长。
- 扩展性差:如果还要增加几种状态,Lift 就需要进行修改,违反了 OCP(开闭原则),但状态模式只需要增加类即可。
总结
状态模式的关键点在于不同的状态下对于同一行为有不同的响应。
状态模式优点:
1.避免了过多的条件语句,使得结构更清晰,提高代码的可维护性
2.可扩展性强,增加状态时只需创建新的状态类,无需修改其他状态类。
3.将特定状态的相关行为封装到一个状态对象中,提供了更好的方法组织与特定状态相关的代码。
状态模式缺点:
1.完全使用状态模式,可能会导致子类会过多
网友评论