State

Назначение

Позволяет изменять объекту изменять свое поведение когда меняется его внутренне состояние, кажется что объект меняет свой класс. Каждое состояние объекта инкапсулируется отдельным классом, каждый из которых определяет поведение объекта в этом состоянии и наследует общий интерфейс/родительский класс.

  • позволяет избавиться от запутанных if-else, switch-case веток
  • Single-responsibility principle: одно состояние – один класс

Описание

Когда требуется изменять поведение методов при изменении внутреннего состояния, обычно пользуются цепочками if-else или выражением switch-case. Но такой подход не расширяем: если добавляется новое состояние, требуется прописывать характерное для него поведение в каждом if-else. Такой код сложно отлаживать и поддерживать, легко запутаться в ветках. Шаблон предлагает извлечь код, который характерен для определенного состояния в отдельные классы, соответствующие этому состоянию. Объект, имеющий внутренне состояние, содержит ссылку на объект, который отражает его состояние. Операции объекта с состоянием делегируются объекту по этой ссылке для того чтобы при изменении ссылки изменилось и поведение этих операций.

Проблема:

  • согласно принципу Interface segregation, интерфейсы должны быть как можно более узкими, объекты не должны через интерфейс зависть от методов, которые он не хочет. В случае State мы обязаны для каждого состояния иметь общего родителя, для того чтобы менять состояние. Но не всегда все методы этого родительского интерфейса применимы для каждого состояния.

Реализация

Ни один класс, за исключением класса State, не должен знать о подклассах класса State. Можно сделать вложенными private классами.

Context
Document
Класс объектов, меняющих свое поведение в зависимости от состояния;
State
DocumentState
Интерфейс состояния, определяющий зависящие от состояния действия, которые можно сделать с объектом;
ConcreteState
Draft
Конкретная реализация интерфейса состояния, которая определяет специфику выполнения описанных действий в этом состоянии;

Примеры

Допустим, документ имеет состояния: Draft, Moderation, Published. В этом случае при реализации шаблона будет создано 3 класса, соответствующих каждому из упомянутых состояний. Конкретный экземпляр состояния инжектится в класс Document, для того чтобы делегировать ему операции, поведение которых меняется.

Варианты

  • Есть вариант с одним методом в интерфейсе и с отдельными методами на каждое событие. Вариант с одним методом принимает аргумент, который опередляет какое событие произошло, а конкретный класс состояния сам решает как среагировать на этот событие. Такой вариант удобнее тем, что при добавлении нового состояния (и соответственно, события) интерфейс State не будет расширен, поэтому не надо будет во всех реализациях добавлять его обработку. Но тут пропадает значительная выгода от полиморфизма: внутри единственного метода приходится ставить if-else или switch чтобы обработать соответствующие событие.
  • можно добавлять callback’и на вход и выход из состояния. Например, можно условитсья что метод enter() интерфейса State всегда вызывается при переходе в это состояние.
  • State может быть и абстрактным классом. Главное, что у нас есть общий тип, через который можно ссылаться на любое из состояний.

Чем отличается

Strategy обычно не знает про наличие других стратегий, в то время как каждое состояние State знает какие ещё бывают состояния и обеспечивает переход в новое соседнее состояние.

Ссылки

https://java-design-patterns.com/patterns/state/

https://github.com/iluwatar/java-design-patterns/tree/master/state

https://refactoring.guru/design-patterns/state

Baeldung - The Observer Pattern in Java

YouTube: State Pattern – Design Patterns (ep 17)