「补课」进行时:设计模式(19)——状态模式
共 7835字,需浏览 16分钟
·
2020-12-17 17:56
1. 前文汇总
2. LOL 中的状态
感觉我天天在用 LOL 举例子,没办法,都已经 S11 了,而我依然在玩这个游戏。
LOL 中的英雄有很多状态,有正常状态,有吃了伟哥一样的加速状态,有被对方套了虚弱的虚弱状态,还有被对方控制的眩晕状态。
下面来看下,在 LOL 中,初始的英雄状态:
public class Hero {
//正常状态
public static final int COMMON = 1;
//加速状态
public static final int SPEED_UP = 2;
//减速状态
public static final int SPEED_DOWN = 3;
//眩晕状态
public static final int SWIM = 4;
//默认是正常状态
private int state = COMMON;
//跑动线程
private Thread runThread;
//设置状态
public void setState(int state) {
this.state = state;
}
//停止跑动
public void stopRun() {
if (isRunning()) runThread.interrupt();
System.out.println("--------------停止跑动---------------");
}
//开始跑动
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
public void run() {
while (!runThread.isInterrupted()) {
try {
hero.run();
} catch (InterruptedException e) {
break;
}
}
}
});
System.out.println("--------------开始跑动---------------");
runThread.start();
}
private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
//英雄类开始奔跑
private void run() throws InterruptedException{
if (state == SPEED_UP) {
System.out.println("--------------加速跑动---------------");
Thread.sleep(2000);//假设加速持续2秒
state = COMMON;
System.out.println("------加速状态结束,变为正常状态------");
}else if (state == SPEED_DOWN) {
System.out.println("--------------减速跑动---------------");
Thread.sleep(2000);//假设减速持续2秒
state = COMMON;
System.out.println("------减速状态结束,变为正常状态------");
}else if (state == SWIM) {
System.out.println("--------------不能跑动---------------");
Thread.sleep(1000);//假设眩晕持续2秒
state = COMMON;
System.out.println("------眩晕状态结束,变为正常状态------");
}else {
//正常跑动则不打印内容
}
}
}
场景类:
public class Client {
public static void main(String[] args) throws InterruptedException {
Hero hero = new Hero();
hero.startRun();
hero.setState(Hero.SPEED_UP);
Thread.sleep(2000);
hero.setState(Hero.SPEED_DOWN);
Thread.sleep(2000);
hero.setState(Hero.SWIM);
Thread.sleep(2000);
hero.stopRun();
}
}
可以看到,我们的英雄在跑动过程中随着状态的改变,我们的英雄会以不同的状态进行跑动。
但是问题也随之而来,我们的英雄类当中有明显的 if else 结构,这并不是我们希望看到的,接下来,我们看下状态模式。
3. 状态模式
3.1 定义
状态模式的定义如下:
Allow an object to alter its behavior when its internal state changes.The object will appear to change its class.(当一个对象内在状态改变时允许其改变行为, 这个对象看起来像改变了其类。)
3.2 通用类图
State 抽象状态角色:接口或抽象类, 负责对象状态定义, 并且封装环境角色以实现状态切换。 ConcreteState 具体状态角色:每一个具体状态必须完成两个职责:本状态的行为管理以及趋向状态处理, 通俗地说,就是本状态下要做的事情, 以及本状态如何过渡到其他状态。 Context 环境角色:定义客户端需要的接口, 并且负责具体状态的切换。
状态模式从类图上看比较简单,实际上还是比较复杂的,它提供了一种对物质运动的另一个观察视角, 通过状态变更促使行为的变化。
类似水的状态变更一样, 一碗水的初始状态是液态, 通过加热转变为、气态, 状态的改变同时也引起体积的扩大, 然后就产生了一个新的行为:鸣笛或顶起壶盖,瓦特就是这么发明蒸汽机的。
3.3 通用代码:
抽象环境角色:
public abstract class State {
// 定义一个环境角色,提供子类访问
protected Context context;
// 设置环境资源
public void setContext(Context context) {
this.context = context;
}
// 行为1
abstract void handle1();
// 行为2
abstract void handle2();
}
具体环境角色:
public class ConcreteState1 extends State {
@Override
void handle1() {
//本状态下必须处理的逻辑
}
@Override
void handle2() {
//设置当前状态为stat2
super.context.setCurrentState(Context.STATE2);
//过渡到state2状态, 由Context实现
super.context.handle2();
}
}
public class ConcreteState2 extends State {
@Override
void handle1() {
//设置当前状态为stat2
super.context.setCurrentState(Context.STATE1);
//过渡到state2状态, 由Context实现
super.context.handle1();
}
@Override
void handle2() {
// 本状态下必须处理的逻辑
}
}
具体环境角色:
public class Context {
final static State STATE1 = new ConcreteState1();
final static State STATE2 = new ConcreteState2();
private State concreteState;
public State getCurrentState() {
return concreteState;
}
//设置当前状态
public void setCurrentState(State currentState) {
this.concreteState = currentState;
//切换状态
this.concreteState.setContext(this);
}
public void handle1(){
this.concreteState.handle1();
}
public void handle2(){
this.concreteState.handle2();
}
}
环境角色有两个不成文的约束:
把状态对象声明为静态常量, 有几个状态对象就声明几个静态常量。 环境角色具有状态抽象角色定义的所有行为, 具体执行使用委托方式。
public class Client {
public static void main(String[] args) {
//定义环境角色
Context context = new Context();
//初始化状态
context.setCurrentState(new ConcreteState1());
//行为执行
context.handle1();
context.handle2();
}
}
这里我们已经隐藏了状态的变化过程, 它的切换引起了行为的变化。对外来说, 我们只看到行为的发生改变, 而不用知道是状态变化引起的。
3.4 优点
避免了过多的 if else 语句的使用,避免了程序的复杂性,提高系统的可维护性。 使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,会非常的容易。 状态是可以被共享的,状态都是由 static final 进行修饰的。
3.5 缺点
有优点的同事也会产生缺点,有时候,优点和缺点的产生其实是同一个事实:
状态模式最主要的一个缺点是:子类会太多,也就是类膨胀。因为一个事物有很多个状态也不稀奇,如果完全使用状态模式就会有太多的子类,不好管理。
4. 案例完善
前面那个 LOL 的例子,如果使用状态模式重写一下,会是这样的:
首先创建一个跑动的接口:
public interface RunState {
void run(Hero hero);
}
接下来是4个实现类,分别实现不同状态的跑动结果:
public class CommonState implements RunState {
@Override
public void run(Hero hero) {
// 正常跑动则不打印内容,否则会刷屏
}
}
public class SpeedUpState implements RunState{
@Override
public void run(Hero hero) {
System.out.println("--------------加速跑动---------------");
try {
Thread.sleep(2000);//假设加速持续2秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------加速状态结束,变为正常状态------");
}
}
public class SpeedDownState implements RunState{
@Override
public void run(Hero hero) {
System.out.println("--------------减速跑动---------------");
try {
Thread.sleep(2000);//假设减速持续2秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------减速状态结束,变为正常状态------");
}
}
public class SwimState implements RunState {
@Override
public void run(Hero hero) {
System.out.println("--------------不能跑动---------------");
try {
Thread.sleep(1000);//假设眩晕持续1秒
} catch (InterruptedException e) {}
hero.setState(Hero.COMMON);
System.out.println("------眩晕状态结束,变为正常状态------");
}
}
最后是一个 Hero(Context) 类:
public class Hero {
public static final RunState COMMON = new CommonState();//正常状态
public static final RunState SPEED_UP = new SpeedUpState();//加速状态
public static final RunState SPEED_DOWN = new SpeedDownState();//减速状态
public static final RunState SWIM = new SwimState();//眩晕状态
private RunState state = COMMON;//默认是正常状态
private Thread runThread;//跑动线程
//设置状态
public void setState(RunState state) {
this.state = state;
}
//停止跑动
public void stopRun() {
if (isRunning()) runThread.interrupt();
System.out.println("--------------停止跑动---------------");
}
//开始跑动
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
public void run() {
while (!runThread.isInterrupted()) {
state.run(hero);
}
}
});
System.out.println("--------------开始跑动---------------");
runThread.start();
}
private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
}
可以看到,这段代码和开头那段代码虽然完成了一样的功能,但是整个代码的复杂度缺以肉眼可见的级别提高了,一般而言,我们牺牲复杂性去换取的高可维护性和扩展性是相当值得的,除非增加了复杂性以后,对于后者的提升会乎其微。