对状态模式的理解

发布于:2025-04-06 ⋅ 阅读:(27) ⋅ 点赞:(0)

一、场景

  • 同一个东西(例如:媒体播放器),有一些操作:暂停、播放、停止。很显然,每执行一种操作,媒体播放器的状态便改变了。

    • 播放 -> 暂停:

      • Media Player is now paused.

      当前是播放状态,点击暂停操作,输出:Media Player is not playing.

    • 暂停 -> 暂停

      • Media Player is not playing.

      当前是暂停状态,点击暂停操作,输出:Media Player is not playing.

    • 虽然是同一种操作,但是,因为媒体播放器处在不同的状态,所以表现不同。

  • 面对这种场景,我们怎么编码呢?

    • 简单粗暴的方式:写if-else或者switch。(详见:二、不采用状态模式)

    • 更好的方式:采用状态模式。(详见:三、采用状态模式)

      • 状态模式是一种行为设计模式, 让我们能在一个对象的内部状态变化时改变其行为。

二、不采用状态模式

1、代码

// 媒体播放器
public class MediaPlayer {
    private String currentState;

    public MediaPlayer() {
        this.currentState = "STOPPED"; // 初始状态为停止
    }

    public void play() {
        if (currentState.equals("STOPPED")) {
            System.out.println("Media Player is now playing.");
            currentState = "PLAYING";
        } else if (currentState.equals("PAUSED")) {
            System.out.println("Media Player resumed playing.");
            currentState = "PLAYING";
        } else {
            System.out.println("Media Player is already playing.");
        }
    }

    public void pause() {
        if (currentState.equals("PLAYING")) {
            System.out.println("Media Player is now paused.");
            currentState = "PAUSED";
        } else {
            System.out.println("Media Player is not playing.");
        }
    }

    public void stop() {
        System.out.println("Media Player is now stopped.");
        currentState = "STOPPED";
    }
}

// 客户端
public class Main {
    public static void main(String[] args) {
        MediaPlayer mediaPlayer = new MediaPlayer();
        System.out.println("Initial state: Stopped");

        mediaPlayer.play(); // Stopped -> Playing
        mediaPlayer.pause(); // Playing -> Paused
        mediaPlayer.play(); // Paused -> Playing
        mediaPlayer.stop(); // Playing -> Stopped
    }
}

/*
Initial state: Stopped
Media Player is now playing.
Media Player is now paused.
Media Player resumed playing.
Media Player is now stopped.
*/

2、缺点

  • 由于在设计之初,不一定能想到所有状态,假设一开始只想到了:play、pause、stop。辛辛苦苦写好了上面的代码。这时候,产品经理又提了新需求了,发现又要补2个状态。没办法,上面的play、pause、stop三个方法都要进行修改。改着改着就成屎山了。

三、采用状态模式

1、代码

1.1 状态类
  • 状态模式建议为对象的所有可能状态新建一个类, 每个状态独自实现自身状态下的各个行为。
// 抽象父类
public abstract class MediaPlayerState {
    protected MediaPlayer mediaPlayer;

    public MediaPlayerState(MediaPlayer mediaPlayer) {
        this.mediaPlayer = mediaPlayer;
    }

    protected abstract void play();
    protected abstract void pause();
    protected abstract void stop();
}

// 播放状态
public class MediaPlayerPlayState extends MediaPlayerState {
    public MediaPlayerPlayState(MediaPlayer mediaPlayer) {
        super(mediaPlayer);
    }

    @Override
    public void play() {
        System.out.println("Media Player is already playing.");
        mediaPlayer.setState(this);
    }

    @Override
    public void pause() {
        System.out.println("Media Player is now paused.");
        mediaPlayer.setState(new MediaPlayerPauseState(mediaPlayer));
    }

    @Override
    public void stop() {
        System.out.println("Media Player is now stopped.");
        mediaPlayer.setState(new MediaPlayerStopState(mediaPlayer));
    }
}

// 暂停状态
public class MediaPlayerPauseState extends MediaPlayerState {
    public MediaPlayerPauseState(MediaPlayer mediaPlayer) {
        super(mediaPlayer);
    }

    @Override
    public void play() {
        System.out.println("Media Player resumed playing.");
        mediaPlayer.setState(new MediaPlayerPlayState(mediaPlayer));
    }

    @Override
    public void pause() {
        System.out.println("Media Player is not playing.");
        mediaPlayer.setState(this);
    }

    @Override
    public void stop() {
        System.out.println("Media Player is now stopped.");
        mediaPlayer.setState(new MediaPlayerStopState(mediaPlayer));
    }
}

// 停止状态
public class MediaPlayerStopState extends MediaPlayerState {
    public MediaPlayerStopState(MediaPlayer mediaPlayer) {
        super(mediaPlayer);
    }

    @Override
    public void play() {
        System.out.println("Media Player is now playing.");
        mediaPlayer.setState(new MediaPlayerPlayState(mediaPlayer));
    }

    @Override
    public void pause() {
        System.out.println("Media Player is not playing.");
        mediaPlayer.setState(new MediaPlayerPauseState(mediaPlayer));
    }

    @Override
    public void stop() {
        System.out.println("Media Player is now stopped.");
        mediaPlayer.setState(this);
    }
}
1.2 上下文(这里指:媒体播放器)
public class MediaPlayer {
    private MediaPlayerState state;

    public void setState(MediaPlayerState state) {
        this.state = state;
    }

    public MediaPlayer() {
        this.state = new MediaPlayerStopState(this);
    }

    public void play() {
        state.play();
    }

    public void pause() {
        state.pause();
    }

    public void stop() {
        state.stop();
    }
}
1.3 客户端
public class Main {
    public static void main(String[] args) {
        MediaPlayer mediaPlayer = new MediaPlayer();
        System.out.println("Initial state: Stopped");

        mediaPlayer.play(); // Stopped -> Playing
        mediaPlayer.pause(); // Playing -> Paused
        mediaPlayer.play(); // Paused -> Playing
        mediaPlayer.stop(); // Playing -> Stopped
    }
}

/*
Initial state: Stopped
Media Player is now playing.
Media Player is now paused.
Media Player resumed playing.
Media Player is now stopped.
*/

2、优点

  • 每个状态类,只需要关心自己状态下,每种操作应该执行哪些逻辑就好了。
  • 如果新增了状态,那无非就是新增一个类,其他已有的状态类,无非就是新写一个方法。这并不会去修改已有的代码,符合开闭原则。

上面的写法有一个小瑕疵,每个状态类实际上应该是单例模式(详见:对单例模式的饿汉式、懒汉式的思考),而不是每次切换状态的时候新建一个对象。

  • 不同状态下,有些操作是一样的,例如:停止操作。这时候可以采用组合的方式进行复用。将停止操作放到一个类中,例如:MediaPlayerStopHandler。每个方法调用MediaPlayerStopHandler的stop方法()实现停止。

  • 仔细看下状态模式的结构:​

    image

    • 会发现和策略模式的结构很像。但二者有很多不同:

      • (1)策略模式:

        • 1)根据客户端的输入,匹配一种策略。每个策略完全独立,策略A感知不到策略B。
        • 2)策略A和策略B是做同一件事情,仅仅是做法不同。
      • (2)状态模式:

        • 1)每种状态下,会有多种行为。(如果只有一种行为,也不存在状态转换了)
        • 2)状态A和状态B不是完全独立的,状态A执行某个行为后,会从状态A转移到状态B。