Состояние — это поведенческий паттерн, позволяющий динамически изменять поведение объекта при смене его состояния.
Поведения, зависящие от состояния, переезжают в отдельные классы. Первоначальный класс хранит ссылку на один из таких объектов-состояний и делегирует ему работу.
Сложность:
Популярность:
Применимость: Паттерн Состояние часто используют в Java для превращения в объекты громоздких стейт-машин, построенных на операторах switch
.
Примеры Состояния в стандартных библиотеках Java:
Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Аудиоплеер
Основной класс плеера меняет своё поведение в зависимости от того, в каком состоянии находится проигрывание.
states states/State.java: Общий интерфейс состояний
package refactoring_guru.state.example.states;
import refactoring_guru.state.example.ui.Player;
/**
* Общий интерфейс всех состояний.
*/
public abstract class State {
Player player;
/**
* Контекст передаёт себя в конструктор состояния, чтобы состояние могло
* обращаться к его данным и методам в будущем, если потребуется.
*/
State(Player player) {
this.player = player;
}
public abstract String onLock();
public abstract String onPlay();
public abstract String onNext();
public abstract String onPrevious();
}
states/LockedState.java: Состояние "заблокирован"
package refactoring_guru.state.example.states;
import refactoring_guru.state.example.ui.Player;
/**
* Конкретные состояния реализуют методы абстрактного состояния по-своему.
*/
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...";
}
}
states/ReadyState.java: Состояние "готов"
package refactoring_guru.state.example.states;
import refactoring_guru.state.example.ui.Player;
/**
* Они также могут переводить контекст в другие состояния.
*/
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...";
}
}
states/PlayingState.java: Состояние "проигрывание"
package refactoring_guru.state.example.states;
import refactoring_guru.state.example.ui.Player;
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();
}
}
ui ui/Player.java: Проигрыватель
package refactoring_guru.state.example.ui;
import refactoring_guru.state.example.states.ReadyState;
import refactoring_guru.state.example.states.State;
import java.util.ArrayList;
import java.util.List;
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;
}
}
ui/UI.java: GUI проигрывателя
package refactoring_guru.state.example.ui;
import javax.swing.*;
import java.awt.*;
public class UI {
private Player player;
private static JTextField textField = new JTextField();
public UI(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);
}
}
Demo.java: Клиентский код
package refactoring_guru.state.example;
import refactoring_guru.state.example.ui.Player;
import refactoring_guru.state.example.ui.UI;
/**
* Демо-класс. Здесь всё сводится воедино.
*/
public class Demo {
public static void main(String[] args) {
Player player = new Player();
UI ui = new UI(player);
ui.init();
}
}
OutputDemo.png: Снимок работы программы