Назначение
Позволяет изменять объекту изменять свое поведение когда меняется его внутренне состояние, кажется что объект меняет свой класс. Каждое состояние объекта инкапсулируется отдельным классом, каждый из которых определяет поведение объекта в этом состоянии и наследует общий интерфейс/родительский класс.
- позволяет избавиться от запутанных
if-else
,switch-case
веток - Single-responsibility principle: одно состояние – один класс
Описание
Когда требуется изменять поведение методов при изменении внутреннего состояния,
обычно пользуются цепочками if-else
или выражением switch-case
. Но такой
подход не расширяем: если добавляется новое состояние, требуется прописывать
характерное для него поведение в каждом if-else
. Такой код сложно отлаживать и
поддерживать, легко запутаться в ветках. Шаблон предлагает извлечь код, который
характерен для определенного состояния в отдельные классы, соответствующие этому
состоянию. Объект, имеющий внутренне состояние, содержит ссылку на объект,
который отражает его состояние. Операции объекта с состоянием делегируются
объекту по этой ссылке для того чтобы при изменении ссылки изменилось и
поведение этих операций.
Проблема:
- согласно принципу Interface segregation, интерфейсы должны быть как можно более узкими, объекты не должны через интерфейс зависть от методов, которые он не хочет. В случае State мы обязаны для каждого состояния иметь общего родителя, для того чтобы менять состояние. Но не всегда все методы этого родительского интерфейса применимы для каждого состояния.
Реализация
Ни один класс, за исключением класса State, не должен знать о подклассах
класса State. Можно сделать вложенными private
классами.
Примеры
Допустим, документ имеет состояния: 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