一、状态模式是什么?
(源于Design Pattern):当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
1.要点:
1.1、有一个对象,它是有状态的。
1.2、这个对象在状态不同的时候,行为不一样。
1.3、这些状态是可以切换的,而非毫无关系。
2.三个角色:
2.1、Context:它就是那个含有状态的对象,它可以处理一些请求,这些请求最终产生的响应会与状态相关。
2.2、State:状态接口,它定义了每一个状态的行为集合,这些行为会在Context中得以使用。
2.3、ConcreteState:具体状态,实现相关行为的具体状态类。
3.状态模式解决的问题: 状态模式主要解决的是当控制一个对象状态的条件表达式过于复杂时的情况。把状态的判断逻辑转移到表示不同状态的一系列类中, 可以把复杂的判断逻辑简化。
二、代码示例
1.未使用状态模式 if-else
举一个例子,LOL里面的英雄的状态,正常、眩晕、加速、减速。
package com.yl.state;
import lombok.Setter;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* 描述: 英雄类-原始版
*
*
* @author: yanglin
* @Date: 2020-07-01-11:28
* @Version: 1.0
*/
@Slf4j
public class Hero {
/**
* 状态:
* 正常,眩晕,加速,减速
*/
/**
* 正常
*/
public static final int NORMAL = 0;
/**
* 加速
*/
public static final int SPEED_UP = 1;
/**
* 减速
*/
public static final int SPEED_CUT = 2;
/**
* 眩晕
*/
public static final int DIZZINESS = 3;
/**
* 默认是正常状态
*/
@Setter
private int state = NORMAL;
/**
* 跑动线程
*/
private Thread runThread;
/**
* 开始跑动
*/
public void startRun() {
if (isRunning()) {
return;
}
final Hero hero = this;
runThread = new Thread(new Runnable() {
@Override
public void run() {
while (!runThread.isInterrupted()) {
/**
* 未使用状态模式
*/
hero.run();
}
}
});
log.info("英雄开始跑动....................");
runThread.start();
}
/**
* 停止跑动
*/
public void stopRun() {
if (isRunning()) {
runThread.interrupt();
}
log.info("英雄停止跑动....................");
}
/**
* 判断线程
*
* @return
*/
private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
/**
* 英雄开始跑动
*/
@SneakyThrows
public void run() {
// 未使用状态模式
if (state == SPEED_UP) {
log.info("英雄开始加速跑动....................");
Thread.sleep(4000);
state = NORMAL;
log.info("英雄开始加速跑动END....................");
}else if (state == SPEED_CUT) {
log.info("英雄开始减速跑动....................");
Thread.sleep(4000);
state = NORMAL;
log.info("英雄开始减速跑动END....................");
}else if (state == DIZZINESS) {
log.info("英雄被DOTA眩晕了....................");
Thread.sleep(2000);
state = NORMAL;
log.info("英雄被DOTA眩晕了END....................");
}
}
public static void main(String[] args) throws InterruptedException {
Hero hero = new Hero();
hero.startRun();
// 未使用状态模式
log.info("英雄 测试 未使用状态模式 .......................");
hero.setState(Hero.SPEED_UP);
Thread.sleep(5000);
hero.setState(Hero.SPEED_CUT);
Thread.sleep(5000);
hero.setState(Hero.DIZZINESS);
Thread.sleep(5000);
hero.stopRun();
}
}
2.使用状态模式
分为三个角色:Context(状态对象)、State(状态接口)、ConcreteState(具体状态实现)。
/**
* 描述: State:状态接口,它定义了每一个状态的行为集合,这些行为会在Context中得以使用。
*
* @author: yanglin
* @Date: 2020-07-01-13:04
* @Version: 1.0
*/
public interface RunState {
void run(HeroState hero);
}
/**
* 描述: ConcreteState:具体状态,实现相关行为的具体状态类。
*
* 眩晕状态
*
* @author: yanglin
* @Date: 2020-07-01-13:15
* @Version: 1.0
*/
@Slf4j
public class DizzinessState implements RunState{
@SneakyThrows
@Override
public void run(HeroState hero) {
log.info("State-英雄被DOTA眩晕了....................");
Thread.sleep(2000);
hero.setState_(HeroState.NORMAL_);
log.info("State-英雄被DOTA眩晕了END....................");
}
}
/**
* 描述: ConcreteState:具体状态,实现相关行为的具体状态类。
*
* 正常状态
*
* @author: yanglin
* @Date: 2020-07-01-13:08
* @Version: 1.0
*/
public class NormalState implements RunState{
@Override
public void run(HeroState hero) {
}
}
/**
* 描述: ConcreteState:具体状态,实现相关行为的具体状态类。
*
* 减速状态
*
* @author: yanglin
* @Date: 2020-07-01-13:13
* @Version: 1.0
*/
@Slf4j
public class SpeedCutState implements RunState{
@SneakyThrows
@Override
public void run(HeroState hero) {
log.info("State-英雄开始减速跑动....................");
Thread.sleep(4000);
hero.setState_(HeroState.NORMAL_);
log.info("State-英雄开始减速跑动END....................");
}
}
/**
* 描述: ConcreteState:具体状态,实现相关行为的具体状态类。
*
* 加速状态
*
* @author: yanglin
* @Date: 2020-07-01-13:10
* @Version: 1.0
*/
@Slf4j
public class SpeedUpState implements RunState{
@SneakyThrows
@Override
public void run(HeroState hero) {
log.info("State-英雄开始加速跑动....................");
Thread.sleep(4000);
hero.setState_(HeroState.NORMAL_);
log.info("State-英雄开始加速跑动END....................");
}
}
/**
* 描述: 英雄类-状态模式
*
*
* @author: yanglin
* @Date: 2020-07-01-11:28
* @Version: 1.0
*/
@Slf4j
public class HeroState{
/**
* 状态:
* 正常,眩晕,加速,减速
*/
/**
* 正常
*/
public static final NormalState NORMAL_ = new NormalState();
/**
* 加速
*/
public static final SpeedUpState SPEED_UP_ = new SpeedUpState();
/**
* 减速
*/
public static final SpeedCutState SPEED_CUT_ = new SpeedCutState();
/**
* 眩晕
*/
public static final DizzinessState DIZZINESS_ = new DizzinessState();
/**
* 默认是正常状态
*/
@Setter
private RunState state_ = NORMAL_;
/**
* 跑动线程
*/
private Thread runThread;
/**
* 开始跑动
*/
public void startRun() {
if (isRunning()) {
return;
}
final HeroState hero = this;
runThread = new Thread(new Runnable() {
@Override
public void run() {
while (!runThread.isInterrupted()) {
/**
* 使用状态模式
*/
state_.run(hero);
}
}
});
log.info("英雄开始跑动....................");
runThread.start();
}
/**
* 停止跑动
*/
public void stopRun() {
if (isRunning()) {
runThread.interrupt();
}
log.info("英雄停止跑动....................");
}
/**
* 判断线程
*
* @return
*/
private boolean isRunning(){
return runThread != null && !runThread.isInterrupted();
}
public static void main(String[] args) throws InterruptedException {
HeroState hero = new HeroState();
hero.startRun();
// 使用状态模式
log.info("英雄 测试 使用状态模式 .......................");
hero.setState_(HeroState.SPEED_UP_);
Thread.sleep(5000);
hero.setState_(HeroState.SPEED_CUT_);
Thread.sleep(5000);
hero.setState_(HeroState.DIZZINESS_);
Thread.sleep(5000);
hero.stopRun();
}
}
上面的代码示例看起来,使用状态模式比使用if-else还复杂一点。但是牺牲复杂性去换取的高可维护性和扩展性是相当值得的。
总结
1.状态模式的优点
1.1、我们去掉了if else结构,使得代码的可维护性更强,不易出错,这个优点挺明显,如果试图让你更改跑动的方法, 是刚才的一堆if else好改,还是分成了若干个具体的状态类好改呢?答案是显而易见的。
1.2、使用多态代替了条件判断,这样我们代码的扩展性更强,比如要增加一些状态,假设有加速20%,加速10%, 减速10%等等等(这并不是虚构,DOTA当中是真实存在这些状态的),会非常的容易。
1.3、状态是可以被共享的,这个在上面的例子当中有体现,看下Hero类当中的四个static final变量就知道了, 因为状态类一般是没有自己的内部状态的,所有它只是一个具有行为的对象,因此是可以被共享的。
1.4、状态的转换更加简单安全,简单体现在状态的分割,因为我们把一堆if else分割成了若干个代码段分别放在 几个具体的状态类当中,所以转换起来当然更简单,而且每次转换的时候我们只需要关注一个固定的状态到其他状态的转换。
安全体现在类型安全,我们设置上下文的状态时,必须是状态接口的实现类,而不是原本的一个整数,这可以杜绝多数以及不正确的状态码。
状态模式适用于某一个对象的行为取决于该对象的状态,并且该对象的状态会在运行时转换,又或者有很多的if else判断, 而这些判断只是因为状态不同而不断的切换行为。
2.状态模式的缺点
2.1、会增加的类的数量。
2.2、使系统的复杂性增加。
尽管状态模式有着这样的缺点,但是往往我们牺牲复杂性去换取的高可维护性和扩展性是相当值得的, 除非增加了复杂性以后,对于后者的提升会乎其微。
状态模式在项目当中也算是较经常会碰到的一个设计模式,但是通常情况下,我们还是在看到if else的情况下, 对项目进行重构时使用,又或者你十分确定要做的项目会朝着状态模式发展,一般情况下,还是不建议在项目的初期使用。
以上。