设计模式:状态模式

意图

状态模式(State)是一种行为设计模式, 让你能在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。

解决方案

状态模式建议为对象的所有可能状态新建一个类, 然后将所有状态的对应行为抽取到这些类中。

原始对象被称为上下文 (context), 它并不会自行实现所有行为, 而是会保存一个指向表示当前状态的状态对象的引用, 且将所有与状态相关的工作委派给该对象。

如需将上下文转换为另外一种状态, 则需将当前活动的状态对象替换为另外一个代表新状态的对象。 采用这种方式是有前提的: 所有状态类都必须遵循同样的接口, 而且上下文必须仅通过接口与这些对象进行交互。

状态模式结构

状态设计模式的结构

上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。

状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。

具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。

状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。

上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。

状态模式适合应用场景

  • 如果对象需要根据自身当前状态进行不同行为, 同时状态的数量非常多且与状态相关的代码会频繁变更的话, 可使用状态模式。

模式建议你将所有特定于状态的代码抽取到一组独立的类中。这样一来,你可以在独立于其他状态的情况下添加新状态或修改已有状态,从而减少维护成本。

  • 如果某个类需要根据成员变量的当前值改变自身行为, 从而需要使用大量的条件语句时, 可使用该模式。

状态模式会将这些条件语句的分支抽取到相应状态类的方法中。 同时, 你还可以清除主要类中与特定状态相关的临时成员变量和帮手方法代码。

  • 当相似状态和基于条件的状态机转换中存在许多重复代码时, 可使用状态模式。

状态模式让你能够生成状态类层次结构, 通过将公用代码抽取到抽象基类中来减少重复。

状态模式优缺点

优点:

  •  单一职责原则。 将与特定状态相关的代码放在单独的类中。
  •  开闭原则。 无需修改已有状态类和上下文就能引入新状态。
  •  通过消除臃肿的状态机条件语句简化上下文代码。

缺点:

  •  如果状态机只有很少的几个状态, 或者很少发生改变, 那么应用该模式可能会显得小题大作。

与其他模式的关系

  • 桥接模式、 状态模式和策略模式 (在某种程度上包括适配器模式) 模式的接口非常相似。 实际上, 它们都基于组合模式——即将工作委派给其他对象, 不过也各自解决了不同的问题。 模式并不只是以特定方式组织代码的配方, 你还可以使用它们来和其他开发者讨论模式所解决的问题。

  • 状态可被视为策略的扩展。 两者都基于组合机制: 它们都通过将部分工作委派给 “帮手” 对象来改变其在不同情景下的行为。 策略使得这些对象相互之间完全独立, 它们不知道其他对象的存在。 但状态模式没有限制具体状态之间的依赖, 且允许它们自行改变在不同情景下的状态。

在 Java 中使用模式

使用示例: 在 Java 语言中, 状态模式通常被用于将基于 switch语句的大型状态机转换为对象。

这里是核心 Java 程序库中一些状态模式的示例:

示例代码

上下文 (Context)

public class Player {
  private State state;
  private boolean playing = false;
  private final 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)

public abstract class State {
  protected final Player player;
  State(Player player) {
    this.player = player;
  }

  public abstract String onLock();
  public abstract String onPlay();
  public abstract String onNext();
  public abstract String onPrevious();
}

具体状态 (Concrete States)

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

 客户端 (Client)

public class Client {
  public static void main(String[] args) {
    Player player = new Player();
    Client ui = new Client(player);
    ui.init();
  }

  private final Player player;
  private static JTextField textField = new JTextField();

  public Client(Player player) {
    this.player = player;
  }

  public void init() {
    JFrame frame = new JFrame("Test player");
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    JPanel context = new JPanel();
    context.setLayout(new BoxLayout(context, BoxLayout.Y_AXIS));
    frame.getContentPane().add(context);
    JPanel buttons = new JPanel(new FlowLayout(FlowLayout.CENTER));
    context.add(textField);
    context.add(buttons);

    JButton play = new JButton("Play");
    play.addActionListener(e -> textField.setText(player.getState().onPlay()));
    JButton stop = new JButton("Stop");
    stop.addActionListener(e -> textField.setText(player.getState().onLock()));
    JButton next = new JButton("Next");
    next.addActionListener(e -> textField.setText(player.getState().onNext()));
    JButton prev = new JButton("Prev");
    prev.addActionListener(e -> textField.setText(player.getState().onPrevious()));
    frame.setVisible(true);
    frame.setSize(300, 100);
    buttons.add(play);
    buttons.add(stop);
    buttons.add(next);
    buttons.add(prev);
  }
}