小白的设计模式之路 - 状态模式 (State pattern)

254 阅读4分钟

介绍

状态模式主要是为了解决有限状态机类型的问题, 为了应付一系列的状态流转间的变化, 避免在代码中出现大量的 if…else 判断

有限状态机 是表示有限个状态以及在这些状态之间的转移和动作等行为的数学计算模型 — 维基百科

image.png

任务管理器案例

假设现在你需要设计一个任务管理器, 其中任务的**状态(state)**有:

  1. 准备 (Ready)
  2. 运行中 (Running)
  3. 结束 (Stop)
  4. 过期 (Expired)

此外, 你有几个可以允许在状态之间变动的动作(action) :

  1. 执行 (Run)
  2. 终止 (Terminal)
  3. 到期 (Expire)

那么我们可以得到一个简单的有限状态机:

image.png

那么接下来如果你要在 Java 中实现一个任务管理器代码的话, 应该会怎么写呢, 最开始我脑海中的第一个想法是:

首先定义一套状态和动作的枚举类

// 任务状态枚举
@AllArgsConstructor
@Getter
enum State {
		READY("准备"),
		RUNIING("运行中"),
		STOP("结束"),
		EXPIRED("过期")
		;
		private final String message;
}

// 行为枚举
@AllArgsConstructor
@Getter
enum Action {
		RUN(1, "开始"),
		TERMIAL(2, "终止"),
		EXPIRE(4, "过期")
		;
		private final int code;
		private final String message;
}

接着实现任务管理器的具体状态

class Task {
    private Long taskId;
    
    // 任务的默认状态为准备
    private State state = State.READY;
    
    // 活动服务
    private ActivityService activityService;
    
    // 任务管理器
    private TaskManager taskManager;
    
    // 使用条件分支进行任务更新
    public void updateState(Action Action) {
        if (state == State.READY) {
            if (Action == Action.RUN) {
                state = State.RUNNING;
            } else if (state == State.EXPIRE) {
            state = State.EXPIRED;
		        }
        } else if (state == State.RUNNING) {
            if (Action == Action.TERMIAL) {
                state = State.STOP;
                // 任务完成后的逻辑 ...
            } else if (Action == Action.EXPIRE) {
                state = State.EXPIRED;
            }
        }
    }
}

这样, 一个简单的任务管理器就完成了, 他可以创建一个状态为 READY 的任务, 然后根据传入的 Action 来更新当前任务的状态, 但是我们应该不难发觉这个类中的一些问题:

  1. 不符合开闭原则, 如果需要新增状态或者动作, 需要修改核心代码
  2. 不仅要修改核心代码, 而且是不是看起来一堆 if…else 分支, 修改难度十分大, 可读性也很低
  3. 内聚性很低, 因为任务完成后或者过期肯定是需要调用其他模块的, 那么核心类的方法做了太多事情

那么这时候就轮到我们的设计模式 - 状态模式(State pattern) 登场了

状态模式

状态模式是对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。状态模式包含以下主要角色:

  • 环境类(Context)角色:也称为上下文,它定义了客户端需要的接口,内部 维护一个当前状态,并负责具体状态的切换
  • 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对 应的行为,可以有一个或多个行为
  • 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需 要的情况下进行状态切换

直接看下使用状态模式重构之后的代码:

 /** 
	* 任务状态抽象接口 
	*/
interface State {
    // 默认实现,不做任何处理
    default void update(Task task, Action Action) {
    }
}

/** 
	* 任务初始状态
	*/
class TaskReady implements State {
    @Override
    public void update(Task task, Action Action) {
        if (Action == Action.RUN) {
            task.setState(new TaskRunning());
        }
    }
}

/** 
	* 任务进行状态
	*/
class TaskRunning implements State {
    private ActivityService activityService;
    private TaskManager taskManager;
    @Override
    public void update(Task task, Action Action) {
        if (Action == Action.TERINIMAL) {
            task.setState(new TaskStop());
            // 完成后逻辑...
        } else if (Action == Action.STOP) {
            task.setState(new TaskPaused());
        } else if (Action == Action.EXPIRE) {
            task.setState(new TaskExpired());
        }
    }
}

/** 
	* 任务完成状态
	*/
class TaskStop implements State {
}
 
/** 
	* 任务过期状态
	*/
class TaskExpired implements State {
}

/** 
	* 任务上下文类
	*/
class Task {
    private Long taskId;
    // 初始化为初始态
    private State state = new TaskReady();
    // 更新状态
    public void updateState(Action Action) {
        state.update(this, Action);
    }
}

在完成代码重构之后可以发现, 原来大量的 if…else 判断逻辑被包装在了每一个具体状态内部, 因此加入需要修改某一状态的变化, 不会影响其他模块

总结

状态模式是一种能够很好应对复杂状态的变化过程的设计模式, 在实际使用中往往会涉及到枚举状态类型, 状态抽象类, 状态实现类和状态上下文的设计和使用