Identification: The State pattern can be recognized by methods that change their behavior depending on the objects’ state. You can confirm identification if you see that this state can be controlled or replaced by other objects, including state objects themselves.
Interface of a media player
In this example, the State pattern lets the same media player controls behave differently, depending on the current playback state. The main class of the player contains a reference to a state object, which performs most of the work for the player. Some actions may end-up replacing the state object with another, which changes the way the player reacts to the user interactions.
states
states/State.java: Common state interface
packagerefactoring_guru.state.example.states;importrefactoring_guru.state.example.ui.Player;/** * Common interface for all states. */publicabstractclassState {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; }publicabstractStringonLock();publicabstractStringonPlay();publicabstractStringonNext();publicabstractStringonPrevious();}
states/LockedState.java
packagerefactoring_guru.state.example.states;importrefactoring_guru.state.example.ui.Player;/** * Concrete states provide the special implementation for all interface methods. */publicclassLockedStateextendsState {LockedState(Player player) { super(player);player.setPlaying(false); } @OverridepublicStringonLock() {if (player.isPlaying()) {player.changeState(newReadyState(player));return"Stop playing"; } else {return"Locked..."; } } @OverridepublicStringonPlay() {player.changeState(newReadyState(player));return"Ready"; } @OverridepublicStringonNext() {return"Locked..."; } @OverridepublicStringonPrevious() {return"Locked..."; }}
states/ReadyState.java
packagerefactoring_guru.state.example.states;importrefactoring_guru.state.example.ui.Player;/** * They can also trigger state transitions in the context. */publicclassReadyStateextendsState {publicReadyState(Player player) { super(player); } @OverridepublicStringonLock() {player.changeState(newLockedState(player));return"Locked..."; } @OverridepublicStringonPlay() {String action =player.startPlayback();player.changeState(newPlayingState(player));return action; } @OverridepublicStringonNext() {return"Locked..."; } @OverridepublicStringonPrevious() {return"Locked..."; }}
packagerefactoring_guru.state.example.ui;importjavax.swing.*;importjava.awt.*;publicclassUI {privatePlayer player;privatestaticJTextField textField =newJTextField();publicUI(Player player) {this.player= player; }publicvoidinit() {JFrame frame =newJFrame("Test player");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);JPanel context =newJPanel();context.setLayout(newBoxLayout(context,BoxLayout.Y_AXIS));frame.getContentPane().add(context);JPanel buttons =newJPanel(new FlowLayout(FlowLayout.CENTER));context.add(textField);context.add(buttons);// Context delegates handling user's input to a state object. Naturally,// the outcome will depend on what state is currently active, since all// states can handle the input differently.JButton play =newJButton("Play");play.addActionListener(e ->textField.setText(player.getState().onPlay()));JButton stop =newJButton("Stop");stop.addActionListener(e ->textField.setText(player.getState().onLock()));JButton next =newJButton("Next");next.addActionListener(e ->textField.setText(player.getState().onNext()));JButton prev =newJButton("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); }}
Demo.java: Initialization code
packagerefactoring_guru.state.example;importrefactoring_guru.state.example.ui.Player;importrefactoring_guru.state.example.ui.UI;/** * Demo class. Everything comes together here. */publicclassDemo {publicstaticvoidmain(String[] args) {Player player =newPlayer();UI ui =newUI(player);ui.init(); }}