State Design Pattern - tenji/ks GitHub Wiki
状态模式是一种行为软件设计模式,允许对象在其内部状态发生变化时改变其行为。这种模式接近有限状态机的概念(finite-state machines, FSM)。状态模式可以解释为策略模式,它能够通过调用模式接口中定义的方法来切换策略。
状态模式跟状态机是什么关系?
状态机是一种行为模型,它由有限数量的状态组成,因此也称为有限状态机(FSM)。状态模式(State Pattern)是实现状态机的其中一种方式,以下是状态机的几种实现方式:
- 嵌套开关(Nested Switch)
- 状态模式(State Pattern)
- 状态表(State Tables)
三种实现方式的细节,传送门
Demo 跟 ConcreteState 是依赖关系;Demo 跟 Player 是关联关系。
public class Player {
private State state;
private boolean playing = false;
private List<String> playlist = new ArrayList<>();
private int currentTrack = 0;
public Player() {
this.state = new ReadyState(this);
setPlaying(true);
for (int i = 1; i <= 12; i++) {
playlist.add("Track " + i);
}
}
public void changeState(State state) {
this.state = state;
}
public State getState() {
return state;
}
public void setPlaying(boolean playing) {
this.playing = playing;
}
public boolean isPlaying() {
return playing;
}
public String startPlayback() {
return "Playing " + playlist.get(currentTrack);
}
public String nextTrack() {
currentTrack++;
if (currentTrack > playlist.size() - 1) {
currentTrack = 0;
}
return "Playing " + playlist.get(currentTrack);
}
public String previousTrack() {
currentTrack--;
if (currentTrack < 0) {
currentTrack = playlist.size() - 1;
}
return "Playing " + playlist.get(currentTrack);
}
public void setCurrentTrackAfterStop() {
this.currentTrack = 0;
}
}
/**
* Common interface for all states.
*/
public abstract class State {
Player player;
/**
* Context passes itself through the state constructor. This may help a
* state to fetch some useful context data if needed.
*/
State(Player player) {
this.player = player;
}
public abstract String onLock();
public abstract String onPlay();
public abstract String onNext();
public abstract String onPrevious();
}
/**
* Concrete states provide the special implementation for all interface methods.
*/
public class LockedState extends State {
LockedState(Player player) {
super(player);
player.setPlaying(false);
}
@Override
public String onLock() {
if (player.isPlaying()) {
player.changeState(new ReadyState(player));
return "Stop playing";
} else {
return "Locked...";
}
}
@Override
public String onPlay() {
player.changeState(new ReadyState(player));
return "Ready";
}
@Override
public String onNext() {
return "Locked...";
}
@Override
public String onPrevious() {
return "Locked...";
}
}
/**
* They can also trigger state transitions in the context.
*/
public class ReadyState extends State {
public ReadyState(Player player) {
super(player);
}
@Override
public String onLock() {
player.changeState(new LockedState(player));
return "Locked...";
}
@Override
public String onPlay() {
String action = player.startPlayback();
player.changeState(new PlayingState(player));
return action;
}
@Override
public String onNext() {
return "Locked...";
}
@Override
public String onPrevious() {
return "Locked...";
}
}
public class PlayingState extends State {
PlayingState(Player player) {
super(player);
}
@Override
public String onLock() {
player.changeState(new LockedState(player));
player.setCurrentTrackAfterStop();
return "Stop playing";
}
@Override
public String onPlay() {
player.changeState(new ReadyState(player));
return "Paused...";
}
@Override
public String onNext() {
return player.nextTrack();
}
@Override
public String onPrevious() {
return player.previousTrack();
}
}
/**
* Demo class. Everything comes together here.
*/
public class Demo {
public static void main(String[] args) {
Player player = new Player();
player.getState().onPlay();
player.getState().onLock();
player.getState().onNext();
player.getState().onPrevious();
}
}
- 符合单一责任原则。将与特定状态相关的代码组织到单独的类中;
- 符合开/闭原则。引入新状态而不更改现有状态类或上下文;
- 通过消除庞大的状态机条件来简化上下文代码。(避免了复杂的条件嵌套)
符合开/闭原则吗?
这个解释是否有问题?引入新状态 NewConcreteState,不是可能会导致状态接口改变(比如新增了接口方法)或者老状态实现改变(比如需要转移到新状态)吗?
- 如果状态机只有几个状态或很少发生变化,那么应用该模式可能会有点杀鸡用牛刀了。
- 复杂的条件逻辑:当条件语句(if-else 或 switch-case)在对象中变得广泛和复杂时,状态模式有助于将特定于状态的行为组织和分离到各个类中,从而增强可读性和可维护性;
- 行为简单同时状态很少:如果你的对象只有几个简单的状态且行为差异极小,则状态模式的开销超过了它的好处。在这种情况下,对象本身内更简单的条件逻辑可能就足够了;
- 性能关键场景:该模式可能会引入额外的对象创建和方法调用,可能会影响性能。如果性能至关重要,那么使用其它的实现方式(比如嵌套条件)可能更合适;
- 过度设计简单的问题:不要仅仅为了使用设计模式而应用模式。如果你的逻辑清晰且无需它即可维护,请坚持使用更简单的解决方案(比如嵌套条件)。
嵌套开关将所有逻辑保留在一处,但当存在大量状态和转换时,代码可能难以阅读。但是它可能比其他方法更安全、更直观、更容易验证(无多态性或解释)。
Kafka 实际样例:
...
状态表方法需要为内容编写某种解释器(如果你对所使用的语言有反射,这可能会更容易),这可能需要预先完成大量工作。正如 Fowler 指出的那样,如果你的状态表与代码分开,你可以修改软件的行为而无需重新编译。然而,这会带来一些安全隐患:该软件的行为基于外部文件的内容。
以下是状态表示例表格:
Source State | Target State | Event | Guard | Action |
---|---|---|---|---|
ScreenOff | ScreenOff | pressButton | powerLow | displayLowPowerMessage |
ScreenOff | ScreenOn | pressButton | !powerLow | |
ScreenOn | ScreenOff | pressButton | ||
ScreenOff | ScreenCharging | plugPower | ||
ScreenOn | ScreenCharging | plugPower | ||
ScreenCharging | ScreenOff | unplugPower |
简单代码示例:
var states = {
STATE_A: 0,
STATE_B: 1,
STATE_C: 2
};
var inputs = {
ACTION_X: 0,
ACTION_Y: 1,
};
var transitionTable = [
// ACTION_X, // ACTION_Y
[states.STATE_B, states.STATE_C], // STATE_A
[states.STATE_C, states.STATE_C], // STATE_B
[states.STATE_A, states.STATE_B], // STATE_C
];
function transition(state, action) {
return transitionTable[state][action];
}
var state = states.STATE_A; // starting state
while (1) {
var action = getAction();
state = transition(state, action);
// do something for state
}
关于两者的比较,传送门
- State pattern Wikipedia
- State Design Pattern geeksforgeeks
- State
- State Design Pattern
- Is there a typical state machine implementation pattern stackoverflow