文章目录
一、状态模式简介
1.1 什么是状态模式?
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变它的行为。这种模式将状态相关的行为抽取到独立的状态类中,使得代码更加清晰、可维护,并且可以在运行时自由切换对象的状态。
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的上下文对象。
1.2 为什么需要状态模式?
在很多场景中,一个对象的行为取决于它的状态,而且它必须在运行时根据状态改变它的行为。考虑一个简单的例子:一个电灯开关。按一下开关,灯亮;再按一下,灯灭。在这个例子中,开关的行为(按下后发生什么)完全取决于它当前的状态(开或关)。
如果不使用状态模式,我们可能会这样实现:
public class Light {
private boolean isOn = false;
public void press() {
if (isOn) {
System.out.println("关灯");
isOn = false;
} else {
System.out.println("开灯");
isOn = true;
}
}
}
这种方法对于简单的状态转换是可行的,但随着状态数量的增加和状态转换逻辑的复杂化,代码会变得难以维护:
public class ComplexLight {
private enum State { OFF, LOW, MEDIUM, HIGH }
private State state = State.OFF;
public void press() {
switch (state) {
case OFF:
System.out.println("低亮度模式");
state = State.LOW;
break;
case LOW:
System.out.println("中亮度模式");
state = State.MEDIUM;
break;
case MEDIUM:
System.out.println("高亮度模式");
state = State.HIGH;
break;
case HIGH:
System.out.println("关灯");
state = State.OFF;
break;
}
}
}
当状态和行为更加复杂时,这种方法会导致大量的条件语句和难以维护的代码。
1.3 状态模式的核心思想
状态模式的核心思想是:
- 将每个状态封装成独立的类,每个类实现一个共同的接口
- 将与状态相关的行为委托给当前状态对象
- 允许状态对象在需要时改变上下文的状态
通过这种方式,我们可以:
- 避免复杂的条件语句
- 使状态转换更加明确
- 容易添加新的状态
- 提高代码的可维护性
二、状态模式的结构
2.1 UML类图
状态模式的UML类图如下:
┌───────────┐ ┌───────────┐
│ Context │◆──────────│ State │
└───────────┘ └───────────┘
│ state │ △
└───────────┘ │
│ request() │ │
└───────────┘ │
┌───────┴───────┐
│ │
┌──────────────┐ ┌──────────────┐
│ ConcreteStateA│ │ConcreteStateB│
└──────────────┘ └──────────────┘
│ handle() │ │ handle() │
└──────────────┘ └──────────────┘
2.2 各个组件的详细说明
-
State(状态)接口:
- 定义所有具体状态类的共同接口
- 封装与Context的一个特定状态相关的行为
-
ConcreteState(具体状态)类:
- 每个类实现一个与Context的某个状态相关的行为
- 实现State接口定义的方法
- 可以访问Context对象,以便在必要时进行状态转换
-
Context(上下文)类:
- 维护一个ConcreteState子类的实例,这个实例定义了当前状态
- 将与状态相关的请求委托给当前的状态对象
- 提供一个接口让状态对象可以访问Context
2.3 交互过程
- Context将与状态相关的请求委托给当前的状态对象
- Context可以将自身传递给状态对象,使得状态对象可以访问Context
- Context是客户端使用的主要接口;客户端通常不直接与状态对象交互
- 客户端或者Context可以触发状态转换
三、状态模式的实现步骤(以Java为例)
让我们通过一个简单的灯光控制示例,一步步实现状态模式:
步骤1:创建状态接口
首先,我们需要定义一个表示状态的接口:
// 状态接口
public interface LightState {
void handlePress(LightSwitch lightSwitch);
String getState();
}
步骤2:实现具体状态类
然后,为每个状态创建一个实现该接口的类:
// 关闭状态
public class OffState implements LightState {
@Override
public void handlePress(LightSwitch lightSwitch) {
System.out.println("打开灯");
lightSwitch.setState(new OnState());
}
@Override
public String getState() {
return "关闭状态";
}
}
// 打开状态
public class OnState implements LightState {
@Override
public void handlePress(LightSwitch lightSwitch) {
System.out.println("关闭灯");
lightSwitch.setState(new OffState());
}
@Override
public String getState() {
return "打开状态";
}
}
步骤3:创建上下文类
接下来,创建一个上下文类,它将使用状态类:
// 上下文:灯光开关
public class LightSwitch {
private LightState currentState;
// 初始状态是关闭的
public LightSwitch() {
this.currentState = new OffState();
}
// 设置当前状态
public void setState(LightState state) {
this.currentState = state;
}
// 按下开关
public void press() {
System.out.println("当前状态:" + currentState.getState());
currentState.handlePress(this);
}
}
步骤4:客户端代码
最后,客户端可以使用上下文对象而不需要直接处理状态:
public class StatePatternDemo {
public static void main(String[] args) {
LightSwitch lightSwitch = new LightSwitch();
// 第一次按下开关:从关闭到打开
lightSwitch.press();
// 第二次按下开关:从打开到关闭
lightSwitch.press();
// 第三次按下开关:从关闭到打开
lightSwitch.press();
}
}
输出结果:
当前状态:关闭状态
打开灯
当前状态:打开状态
关闭灯
当前状态:关闭状态
打开灯
四、状态模式的各种实现方式
状态模式有多种实现方式,下面我们来看看几种常见的变体:
4.1 状态转换的控制权
状态转换的控制权可以放在不同的地方:
1. 状态类控制转换(自治型)
在这种方式中,状态类自己决定何时以及如何转换到其他状态:
public class SelfDrivenState implements State {
@Override
public void handle(Context context) {
// 处理请求
doSomething();
// 自己决定状态转换
context.setState(new NextState());
}
}
2. 上下文类控制转换(外部驱动型)
在这种方式中,上下文类决定状态转换:
public class Context {
private State state;
public void request() {
state.handle(this);
// 上下文决定状态转换
if (someCondition()) {
setState(new StateA());
} else {
setState(new StateB());
}
}
}
4.2 状态对象的创建时机
状态对象可以在不同时间创建:
1. 预先创建所有状态对象(单例模式)
public class Context {
// 预先创建所有状态对象
private static final State STATE_A = new ConcreteStateA();
private static final State STATE_B = new ConcreteStateB();
private State currentState = STATE_A;
public void setState(State state) {
this.currentState = state;
}
public void requestA() {
// 使用预先创建的状态对象
setState(STATE_B);
}
}
2. 按需创建状态对象
public class Context {
private State state;
public void setState(State state) {
this.state = state;
}
public void request() {
// 需要时创建新的状态对象
setState(new ConcreteStateB());
}
}
4.3 状态转换表驱动的实现
对于复杂的状态机,可以使用状态转换表来定义状态转换规则:
public class StateMachine {
// 状态转换表:当前状态 -> 事件 -> 下一个状态
private Map<State, Map<Event, State>> transitionTable = new HashMap<>();
private State currentState;
public StateMachine() {
// 初始化状态转换表
Map<Event, State> transitions = new HashMap<>();
transitions.put(Event.EVENT_A, State.STATE_B);
transitionTable.put(State.STATE_A, transitions);
// 设置初始状态
currentState = State.STATE_A;
}
public void handleEvent(Event event) {
// 查找下一个状态
State nextState = transitionTable.get(currentState).get(event);
if (nextState != null) {
System.out.println("从 " + currentState + " 转换到 " + nextState);
currentState = nextState;
} else {
System.out.println("在 " + currentState + " 状态下不能处理 " + event);
}
}
}
五、状态模式的多个Java示例
下面我们来看几个不同复杂度的Java实现示例:
5.1 简单示例:电灯开关
这是一个基本的状态模式实现,展示了一个简单的电灯开关系统。
// 状态接口
interface State {
void handle(LightSwitch context);
String getStateName();
}
// 关闭状态
class OffState implements State {
@Override
public void handle(LightSwitch context) {
System.out.println("开灯");
context.setState(new OnState());
}
@Override
public String getStateName() {
return "关闭状态";
}
}
// 打开状态
class OnState implements State {
@Override
public void handle(LightSwitch context) {
System.out.println("关灯");
context.setState(new OffState());
}
@Override
public String getStateName() {
return "打开状态";
}
}
// 上下文类
class LightSwitch {
private State currentState;
public LightSwitch() {
currentState = new OffState(); // 初始状态为关闭
}
public void setState(State state) {
this.currentState = state;
}
public void press() {
System.out.println("当前是:" + currentState.getStateName());
currentState.handle(this);
}
}
// 客户端代码
public class LightExample {
public static void main(String[] args) {
LightSwitch lightSwitch = new LightSwitch();
// 按下开关三次
lightSwitch.press();
lightSwitch.press();
lightSwitch.press();
}
}
输出:
当前是:关闭状态
开灯
当前是:打开状态
关灯
当前是:关闭状态
开灯
5.2 中等复杂度示例:多模式电灯
这个示例展示了一个有多种模式的电灯,每次按下开关都会切换到不同的亮度模式:
// 状态接口
interface LightState {
void press(MultiModeLamp lamp);
String getBrightness();
}
// 关闭状态
class OffState implements LightState {
@Override
public void press(MultiModeLamp lamp) {
System.out.println("切换到低亮度模式");
lamp.setState(new LowState());
}
@Override
public String getBrightness() {
return "关闭";
}
}
// 低亮度状态
class LowState implements LightState {
@Override
public void press(MultiModeLamp lamp) {
System.out.println("切换到中亮度模式");
lamp.setState(new MediumState());
}
@Override
public String getBrightness() {
return "低亮度";
}
}
// 中亮度状态
class MediumState implements LightState {
@Override
public void press(MultiModeLamp lamp) {
System.out.println("切换到高亮度模式");
lamp.setState(new HighState());
}
@Override
public String getBrightness() {
return "中亮度";
}
}
// 高亮度状态
class HighState implements LightState {
@Override
public void press(MultiModeLamp lamp) {
System.out.println("关闭电灯");
lamp.setState(new OffState());
}
@Override
public String getBrightness() {
return "高亮度";
}
}
// 上下文:多模式电灯
class MultiModeLamp {
private LightState state;
public MultiModeLamp() {
this.state = new OffState(); // 初始状态为关闭
}
public void setState(LightState state) {
this.state = state;
}
public void pressButton() {
System.out.println("当前亮度:" + state.getBrightness());
state.press(this);
}
}
// 客户端代码
public class MultiModeLampExample {
public static void main(String[] args) {
MultiModeLamp lamp = new MultiModeLamp();
// 连续按五次开关
lamp.pressButton(); // 从关闭到低亮度
lamp.pressButton(); // 从低亮度到中亮度
lamp.pressButton(); // 从中亮度到高亮度
lamp.pressButton(); // 从高亮度到关闭
lamp.pressButton(); // 从关闭到低亮度
}
}
输出:
当前亮度:关闭
切换到低亮度模式
当前亮度:低亮度
切换到中亮度模式
当前亮度:中亮度
切换到高亮度模式
当前亮度:高亮度
关闭电灯
当前亮度:关闭
切换到低亮度模式
5.3 高级示例:音乐播放器
下面是一个更复杂的例子,展示了一个音乐播放器的不同状态:
// 状态接口
interface PlayerState {
void play(MusicPlayer player);
void pause(MusicPlayer player);
void stop(MusicPlayer player);
void next(MusicPlayer player);
void previous(MusicPlayer player);
String getStateName();
}
// 播放状态
class PlayingState implements PlayerState {
@Override
public void play(MusicPlayer player) {
System.out.println("已经在播放中...");
}
@Override
public void pause(MusicPlayer player) {
System.out.println("暂停播放");
player.setState(new PausedState());
}
@Override
public void stop(MusicPlayer player) {
System.out.println("停止播放");
player.setState(new StoppedState());
}
@Override
public void next(MusicPlayer player) {
System.out.println("播放下一首歌曲");
player.nextSong();
}
@Override
public void previous(MusicPlayer player) {
System.out.println("播放上一首歌曲");
player.previousSong();
}
@Override
public String getStateName() {
return "播放中";
}
}
// 暂停状态
class PausedState implements PlayerState {
@Override
public void play(MusicPlayer player) {
System.out.println("继续播放");
player.setState(new PlayingState());
}
@Override
public void pause(MusicPlayer player) {
System.out.println("已经暂停...");
}
@Override
public void stop(MusicPlayer player) {
System.out.println("停止播放");
player.setState(new StoppedState());
}
@Override
public void next(MusicPlayer player) {
System.out.println("选择下一首歌曲(但不播放)");
player.nextSong();
}
@Override
public void previous(MusicPlayer player) {
System.out.println("选择上一首歌曲(但不播放)");
player.previousSong();
}
@Override
public String getStateName() {
return "已暂停";
}
}
// 停止状态
class StoppedState implements PlayerState {
@Override
public void play(MusicPlayer player) {
System.out.println("开始播放");
player.setState(new PlayingState());
}
@Override
public void pause(MusicPlayer player) {
System.out.println("停止状态下无法暂停");
}
@Override
public void stop(MusicPlayer player) {
System.out.println("已经停止...");
}
@Override
public void next(MusicPlayer player) {
System.out.println("选择下一首歌曲(但不播放)");
player.nextSong();
}
@Override
public void previous(MusicPlayer player) {
System.out.println("选择上一首歌曲(但不播放)");
player.previousSong();
}
@Override
public String getStateName() {
return "已停止";
}
}
// 上下文:音乐播放器
class MusicPlayer {
private PlayerState state;
private String currentSong;
private List<String> playlist;
private int currentIndex;
public MusicPlayer() {
this.state = new StoppedState();
this.playlist = new ArrayList<>(List.of("歌曲1", "歌曲2", "歌曲3", "歌曲4", "歌曲5"));
this.currentIndex = 0;
this.currentSong = playlist.get(currentIndex);
}
public void setState(PlayerState state) {
this.state = state;
}
public void play() {
state.play(this);
}
public void pause() {
state.pause(this);
}
public void stop() {
state.stop(this);
}
public void next() {
state.next(this);
}
public void previous() {
state.previous(this);
}
public void nextSong() {
currentIndex = (currentIndex + 1) % playlist.size();
currentSong = playlist.get(currentIndex);
System.out.println("当前歌曲: " + currentSong);
}
public void previousSong() {
currentIndex = (currentIndex - 1 + playlist.size()) % playlist.size();
currentSong = playlist.get(currentIndex);
System.out.println("当前歌曲: " + currentSong);
}
public void showStatus() {
System.out.println("播放器状态: " + state.getStateName());
System.out.println("当前歌曲: " + currentSong);
}
}
// 客户端代码
public class MusicPlayerExample {
public static void main(String[] args) {
MusicPlayer player = new MusicPlayer();
// 显示初始状态
player.showStatus();
// 开始播放
System.out.println("\n--- 开始播放 ---");
player.play();
player.showStatus();
// 下一首
System.out.println("\n--- 下一首 ---");
player.next();
player.showStatus();
// 暂停
System.out.println("\n--- 暂停 ---");
player.pause();
player.showStatus();
// 暂停状态下切换歌曲
System.out.println("\n--- 暂停状态下切换歌曲 ---");
player.previous();
player.showStatus();
// 继续播放
System.out.println("\n--- 继续播放 ---");
player.play();
player.showStatus();
// 停止
System.out.println("\n--- 停止 ---");
player.stop();
player.showStatus();
}
}
输出:
播放器状态: 已停止
当前歌曲: 歌曲1
--- 开始播放 ---
开始播放
播放器状态: 播放中
当前歌曲: 歌曲1
--- 下一首 ---
播放下一首歌曲
当前歌曲: 歌曲2
播放器状态: 播放中
当前歌曲: 歌曲2
--- 暂停 ---
暂停播放
播放器状态: 已暂停
当前歌曲: 歌曲2
--- 暂停状态下切换歌曲 ---
选择上一首歌曲(但不播放)
当前歌曲: 歌曲1
播放器状态: 已暂停
当前歌曲: 歌曲1
--- 继续播放 ---
继续播放
播放器状态: 播放中
当前歌曲: 歌曲1
--- 停止 ---
停止播放
播放器状态: 已停止
当前歌曲: 歌曲1
六、状态模式的进阶内容
6.1 如何处理状态转换的历史记录
在某些应用中,需要记录状态的变化历史,并允许回退到之前的状态。这可以通过结合备忘录模式(Memento Pattern)来实现:
// 备忘录类:保存状态快照
class PlayerMemento {
private final PlayerState state;
private final String currentSong;
private final int currentIndex;
public PlayerMemento(PlayerState state, String currentSong, int currentIndex) {
this.state = state;
this.currentSong = currentSong;
this.currentIndex = currentIndex;
}
public PlayerState getState() {
return state;
}
public String getCurrentSong() {
return currentSong;
}
public int getCurrentIndex() {
return currentIndex;
}
}
// 修改MusicPlayer类,添加历史记录功能
class MusicPlayerWithHistory {
private PlayerState state;
private String currentSong;
private List<String> playlist;
private int currentIndex;
private Stack<PlayerMemento> history;
public MusicPlayerWithHistory() {
this.state = new StoppedState();
this.playlist = new ArrayList<>(List.of("歌曲1", "歌曲2", "歌曲3", "歌曲4", "歌曲5"));
this.currentIndex = 0;
this.currentSong = playlist.get(currentIndex);
this.history = new Stack<>();
}
// 保存当前状态
public void saveState() {
history.push(new PlayerMemento(state, currentSong, currentIndex));
System.out.println("保存当前状态");
}
// 恢复到上一个状态
public void undo() {
if (!history.isEmpty()) {
PlayerMemento memento = history.pop();
this.state = memento.getState();
this.currentSong = memento.getCurrentSong();
this.currentIndex = memento.getCurrentIndex();
System.out.println("回退到上一个状态");
} else {
System.out.println("没有历史状态可以回退");
}
}
// ... 其他方法同MusicPlayer类 ...
}
6.2 状态模式与单例模式结合
对于那些不需要维护内部状态的状态类,可以将其实现为单例,以减少对象创建的开销:
// 使用单例模式实现状态类
class SingletonState implements State {
private static final SingletonState INSTANCE = new SingletonState();
private SingletonState() {}
public static SingletonState getInstance() {
return INSTANCE;
}
@Override
public void handle(Context context) {
// 处理逻辑...
}
}
// 上下文类
class ContextWithSingletonStates {
private State currentState;
// 使用单例状态
public ContextWithSingletonStates() {
this.currentState = SingletonState.getInstance();
}
// ... 其他方法 ...
}
6.3 使用枚举实现状态模式
在Java中,可以利用枚举来实现状态模式,这是一种简洁的方式:
// 使用枚举实现状态模式
public class EnumStateMachine {
// 使用枚举定义状态和行为
public enum State {
OFF {
@Override
public void press(EnumStateMachine machine) {
System.out.println("开灯");
machine.setState(ON);
}
},
ON {
@Override
public void press(EnumStateMachine machine) {
System.out.println("关灯");
machine.setState(OFF);
}
};
// 抽象方法,由每个枚举值实现
public abstract void press(EnumStateMachine machine);
}
private State currentState = State.OFF;
public void setState(State state) {
this.currentState = state;
}
public void pressButton() {
currentState.press(this);
}
public static void main(String[] args) {
EnumStateMachine machine = new EnumStateMachine();
machine.pressButton(); // 开灯
machine.pressButton(); // 关灯
machine.pressButton(); // 开灯
}
}
输出:
播放器状态: 已停止
当前歌曲: 歌曲1
--- 开始播放 ---
开始播放
播放器状态: 播放中
当前歌曲: 歌曲1
--- 下一首 ---
播放下一首歌曲
当前歌曲: 歌曲2
播放器状态: 播放中
当前歌曲: 歌曲2
--- 暂停 ---
暂停播放
播放器状态: 已暂停
当前歌曲: 歌曲2
--- 暂停状态下切换歌曲 ---
选择上一首歌曲(但不播放)
当前歌曲: 歌曲1
播放器状态: 已暂停
当前歌曲: 歌曲1
--- 继续播放 ---
继续播放
播放器状态: 播放中
当前歌曲: 歌曲1
--- 停止 ---
停止播放
播放器状态: 已停止
当前歌曲: 歌曲1
6.4 使用函数式接口实现状态模式
在Java 8及以上版本,可以使用函数式接口和Lambda表达式来实现状态模式:
import java.util.function.Consumer;
public class FunctionalStateMachine {
// 使用函数式接口定义状态
private Consumer<FunctionalStateMachine> currentState;
// 定义各种状态
private static final Consumer<FunctionalStateMachine> OFF_STATE = machine -> {
System.out.println("开灯");
machine.setState(ON_STATE);
};
private static final Consumer<FunctionalStateMachine> ON_STATE = machine -> {
System.out.println("关灯");
machine.setState(OFF_STATE);
};
public FunctionalStateMachine() {
this.currentState = OFF_STATE;
}
public void setState(Consumer<FunctionalStateMachine> state) {
this.currentState = state;
}
public void pressButton() {
currentState.accept(this);
}
public static void main(String[] args) {
FunctionalStateMachine machine = new FunctionalStateMachine();
machine.pressButton(); // 开灯
machine.pressButton(); // 关灯
machine.pressButton(); // 开灯
}
}
七、状态模式的常见问题与最佳实践
7.1 状态爆炸问题
当状态数量过多时,会导致类的数量剧增。可以采取以下措施缓解:
- 状态分组:将相似的状态归类,实现一个基类处理共同行为
- 使用状态表驱动:对于简单的状态转换,可以使用状态转换表而不是创建大量类
- 混合使用状态模式和策略模式:只为那些行为差异显著的状态创建单独的类
// 状态分组示例
abstract class PlayerBaseState implements PlayerState {
// 提供默认实现
@Override
public void play(MusicPlayer player) {
System.out.println("非法操作");
}
@Override
public void pause(MusicPlayer player) {
System.out.println("非法操作");
}
@Override
public void stop(MusicPlayer player) {
System.out.println("非法操作");
}
// 所有状态共享的默认实现
@Override
public void next(MusicPlayer player) {
System.out.println("选择下一首歌曲");
player.nextSong();
}
@Override
public void previous(MusicPlayer player) {
System.out.println("选择上一首歌曲");
player.previousSong();
}
}
// 具体状态只需要覆盖与默认行为不同的方法
class PlayingState extends PlayerBaseState {
@Override
public void pause(MusicPlayer player) {
System.out.println("暂停播放");
player.setState(new PausedState());
}
@Override
public void stop(MusicPlayer player) {
System.out.println("停止播放");
player.setState(new StoppedState());
}
@Override
public String getStateName() {
return "播放中";
}
}
7.2 状态模式与策略模式的区别
状态模式和策略模式都允许对象在运行时改变行为,但它们的目的不同。状态模式的主要目的是管理对象的状态,而策略模式的主要目的是选择算法。
状态模式中的状态类通常是互斥的,而策略模式中的策略类通常是可互换的。
7.3 状态模式与观察者模式的区别
状态模式和观察者模式都涉及对象之间的通信,但它们的目的不同。状态模式中的状态类通常是互斥的,而观察者模式中的观察者类通常是松耦合的。
状态模式中的状态类通常是静态的,而观察者模式中的观察者类通常是动态的。
7.4 状态模式与责任链模式的区别
状态模式和责任链模式都涉及对象之间的通信,但它们的目的不同。状态模式中的状态类通常是互斥的,而责任链模式中的处理者类通常是链式的。
状态模式中的状态类通常是静态的,而责任链模式中的处理者类通常是动态的。
7.5 状态模式与备忘录模式的区别
状态模式和备忘录模式都涉及对象之间的通信,但它们的目的不同。状态模式中的状态类通常是互斥的,而备忘录模式中的备忘录类通常是静态的。
状态模式中的状态类通常是静态的,而备忘录模式中的备忘录类通常是动态的。
7.6 状态模式与单例模式的区别
状态模式和单例模式都涉及对象之间的通信,但它们的目的不同。状态模式中的状态类通常是互斥的,而单例模式中的单例类通常是静态的。
状态模式中的状态类通常是静态的,而单例模式中的单例类通常是动态的。
7.7 状态跟踪和调试
复杂的状态机往往难以调试。可以添加日志帮助跟踪状态转换:
public void setState(State state) {
System.out.println("状态从 " + this.state.getClass().getSimpleName() +
" 转换到 " + state.getClass().getSimpleName());
this.state = state;
}
7.8 状态模式与观察者模式结合
当状态改变时,可能需要通知其他对象。这时可以结合观察者模式:
public void setState(State state) {
State oldState = this.state;
this.state = state;
// 通知所有观察者
for (StateObserver observer : observers) {
observer.onStateChanged(oldState, state);
}
}
7.9 防止无限状态转换
在某些实现中,状态对象可能不小心创建了递归调用或无限循环。为避免这种情况:
- 仔细设计状态转换逻辑
- 给状态转换添加计数器或标记
- 在复杂情况下,可以使用状态图正式验证状态机的正确性
public void setState(State state) {
// 避免在短时间内反复转换相同的状态
if (this.state.getClass() == state.getClass()) {
stateChangeCounter++;
if (stateChangeCounter > MAX_STATE_CHANGES) {
System.out.println("警告:检测到可能的无限状态转换");
return;
}
} else {
stateChangeCounter = 0;
}
this.state = state;
}
八、状态模式的适用场景与不适用场景
8.1 适用场景
-
对象行为依赖于其状态且必须在运行时根据状态改变行为
- 例如:音乐播放器在播放、暂停和停止状态下的不同行为
-
操作中包含大量条件语句,这些条件语句依赖于对象的状态
- 例如:替换冗长的if-else或switch语句
-
状态转换具有复杂的规则
- 例如:工作流系统中的文档状态转换
-
状态机模型
- 例如:游戏角色状态、网络连接状态、UI界面状态等
8.2 不适用场景
-
状态数量少且状态转换简单
- 使用简单的条件语句可能更清晰
-
状态几乎不变或很少变化
- 引入状态模式会增加不必要的复杂性
-
状态转换与具体状态无关
- 如果状态转换完全由外部决定,可能不需要复杂的状态模式
九、状态模式与其他相关模式的对比
9.1 状态模式 vs 策略模式
两者都涉及改变对象的行为,但目的不同:
-
状态模式:
- 状态对象封装与特定状态相关的行为
- 状态对象通常知道其他状态对象
- 状态转换通常由状态对象或上下文自动触发
- 目的是让对象的行为随状态变化而变化
-
策略模式:
- 策略对象封装不同的算法
- 策略对象通常互不了解
- 策略切换通常由客户端显式指定
- 目的是让算法可以互相替换
9.2 状态模式 vs 命令模式
-
状态模式:
- 对象行为依赖于当前状态
- 状态对象通常负责状态转换
- 关注点在于对象的状态和行为如何随状态变化
-
命令模式:
- 将请求封装为对象
- 命令对象通常不关心接收者的状态
- 关注点在于解耦请求发送者和接收者
9.3 状态模式 vs 备忘录模式
-
状态模式:
- 关注对象行为如何随状态变化
- 实现状态转换逻辑
-
备忘录模式:
- 关注保存和恢复对象状态
- 不涉及对象行为的变化
两者经常结合使用,状态模式管理状态转换,备忘录模式提供状态的保存和恢复功能。
十、常见问题解答
10.1 初学者常见问题
问题1:状态转换应该放在状态类中还是上下文类中?
答:这取决于设计需求。如果状态转换规则与当前状态密切相关,放在状态类中更合适;如果状态转换由外部条件决定,放在上下文类中更合适。实际上,两种方式可以混合使用。
问题2:如何避免状态类之间的相互依赖?
答:
- 让上下文类负责状态转换
- 使用状态转换表
- 使用状态工厂创建新状态
问题3:如何在初始化时避免创建所有可能的状态对象?
答:
- 使用懒加载方式,仅在需要时创建状态对象
- 对于无状态的状态类,使用单例模式
- 使用享元模式共享状态对象
问题4:状态模式与有限状态机有什么区别?
答:状态模式是面向对象实现有限状态机的一种方式。传统的有限状态机通常使用表驱动或大量的条件语句实现,而状态模式通过类和对象的方式提供了更加灵活和可扩展的实现。
10.2 进阶问题
问题1:如何处理状态共享数据?
答:有几种方法:
- 将共享数据存储在上下文中,并提供访问方法
- 使用共享状态对象
- 将状态数据与状态行为分离
问题2:如何实现层次化状态机?
答:
- 使用组合模式组织状态的层次结构
- 实现状态继承体系
- 状态委托:子状态可以将不处理的请求委托给父状态
问题3:如何优化大量状态类导致的代码冗余?
答:
- 使用状态基类提供通用行为
- 合并相似的状态
- 使用状态转换表代替部分状态类
- 考虑使用其他模式如模板方法或策略模式结合使用
十一、真实世界中的状态模式应用
11.1 Java中的状态模式实例
-
Java线程状态
Java的Thread类实现了一个状态机,线程可以处于以下状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED。
-
Java中的Socket连接
Socket对象也实现了状态模式,它可以处于未连接、已连接、关闭等不同状态。
-
Spring框架中的Bean生命周期
Spring Bean的生命周期管理使用了状态模式的概念,Bean可以处于初始化、就绪、销毁等不同状态。
11.2 Android中的Fragment生命周期
Android中的Fragment组件拥有复杂的生命周期状态机,包括创建、启动、恢复、暂停、停止和销毁等状态。
11.3 游戏开发中的角色状态
游戏开发中广泛使用状态模式来管理角色状态,例如站立、行走、跳跃、攻击、受伤、死亡等状态。
十二、总结与最佳实践
12.1 状态模式的核心优势
- 消除了复杂的条件分支语句
- 提高了代码的可维护性和可扩展性
- 使状态转换更加明确,减少了出错可能
- 符合开闭原则,便于添加新状态
12.2 实现状态模式的最佳实践
-
明确状态与行为的对应关系
- 仔细分析对象在不同状态下的行为差异
- 确保状态接口定义了所有需要随状态变化的行为
-
合理选择状态转换的控制位置
- 根据项目需求决定状态转换是由状态类控制还是上下文控制
- 对于复杂的转换逻辑,考虑使用状态转换表
-
状态类的设计与实现
- 保持状态类的简单性和专注性
- 对于共享行为,使用继承或组合减少代码冗余
- 考虑状态对象的生命周期,适当时使用单例或对象池
-
状态模式的测试与维护
- 为每个状态编写单元测试
- 适当添加日志帮助调试和追踪状态转换
- 使用状态图文档化状态转换逻辑
12.3 最终思考
状态模式是一种强大的设计模式,特别适合处理对象行为随状态变化的场景。通过将状态相关的行为封装到专门的类中,它提供了一种结构化管理复杂状态逻辑的方法。
然而,它也带来了额外的复杂性和更多的类。因此,在应用状态模式时,需要权衡其带来的好处与成本,选择适合项目需求的实现方式。
对于状态较少且转换简单的场景,使用简单的条件语句可能更加清晰;而对于状态复杂且需要频繁扩展的系统,状态模式则是更好的选择。