State Design Pattern - tenji/ks GitHub Wiki

状态模式

状态模式是一种行为软件设计模式,允许对象在其内部状态发生变化时改变其行为。这种模式接近有限状态机的概念(finite-state machines, FSM)。状态模式可以解释为策略模式,它能够通过调用模式接口中定义的方法来切换策略。

状态模式跟状态机是什么关系?

状态机是一种行为模型,它由有限数量的状态组成,因此也称为有限状态机(FSM)。状态模式(State Pattern)是实现状态机的其中一种方式,以下是状态机的几种实现方式:

  • 嵌套开关(Nested Switch)
  • 状态模式(State Pattern)
  • 状态表(State Tables)

三种实现方式的细节,传送门

一、结构

state-structure

二、代码样例

state-demo-structure

Demo 跟 ConcreteState 是依赖关系;Demo 跟 Player 是关联关系。

Player 类(Context)

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;
    }
}

State 类(State Interface)

/**
 * 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();
}

LockedState 类(ConcreteState1)

/**
 * 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...";
    }
}

ReadyState 类(ConcreteState2)

/**
 * 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...";
    }
}

PlayingState 类(ConcreteState3)

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();
    }
}

Driver Program

/**
 * 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)在对象中变得广泛和复杂时,状态模式有助于将特定于状态的行为组织和分离到各个类中,从而增强可读性和可维护性;

六、什么时候不适合使用状态模式?

  • 行为简单同时状态很少:如果你的对象只有几个简单的状态且行为差异极小,则状态模式的开销超过了它的好处。在这种情况下,对象本身内更简单的条件逻辑可能就足够了;
  • 性能关键场景:该模式可能会引入额外的对象创建和方法调用,可能会影响性能。如果性能至关重要,那么使用其它的实现方式(比如嵌套条件)可能更合适;
  • 过度设计简单的问题:不要仅仅为了使用设计模式而应用模式。如果你的逻辑清晰且无需它即可维护,请坚持使用更简单的解决方案(比如嵌套条件)。

七、状态机的三种实现方式

7.1 嵌套开关(Nested Switch)

嵌套开关将所有逻辑保留在一处,但当存在大量状态和转换时,代码可能难以阅读。但是它可能比其他方法更安全、更直观、更容易验证(无多态性或解释)。

Kafka 实际样例:

7.2 状态模式(State Pattern)

...

7.3 状态表(State Tables)

状态表方法需要为内容编写某种解释器(如果你对所使用的语言有反射,这可能会更容易),这可能需要预先完成大量工作。正如 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
}

八、状态模式 vs 策略模式

关于两者的比较,传送门

参考链接

⚠️ **GitHub.com Fallback** ⚠️