Состояние (State): изменение поведения объектов
Объект, который проявляет себя в изменении своего класса.
Указание: условный код почти для всех методов.
Шаблон Состояние(State) переключает с одной реализации на другую во время жизненного цикла суррогата для того, чтобы воспроизвести различное поведение при вызове одинаковых методов. Есть способ улучшить реализацию вашего кода, когда вы сделаете много циклов тестирования каждого вашего метода прежде, чем решите что нужно сделать с тем или иным методом. Например, сказка о царевне-лягушке содержит объект (живое существо), которое ведет себя различным образом в зависимости от своего состояния. Вы должны реализовать это, используя булевскую переменную для проверки:
//: state:KissingPrincess.java
package state;
class Creature private boolean isFrog = true ;
public void greet () if ( isFrog )
System.out.println ( «Ribbet!» ) ;
else
System.out.println ( «Darling!» ) ;
>
public void kiss () isFrog = false ;
>
>
public class KissingPrincess extends TestCase Creature creature = new Creature () ;
public void test () creature.greet () ;
creature.kiss () ;
creature.greet () ;
>
public static void main ( String args []) junit.textui.TestRunner.run ( KissingPrincess. class ) ;
>
> // /:~
Однако, метод greet() и любой другой метод, который должен проверять переменную isFrog прежде, чем выполнит свою операцию, будет заключать в себе неуклюжий код. При делегировании операции объекту State, объект может быть изменен, что упростит код.
//: state:KissingPrincess2.java
package state;
class Creature private interface State String response () ;
>
private class Frog implements State public String response () return «Ribbet!» ;
>
>
private class Prince implements State public String response () return «Darling!» ;
>
>
private State state = new Frog () ;
public void greet () System.out.println ( state.response ()) ;
>
public void kiss () state = new Prince () ;
>
>
public class KissingPrincess2 extends TestCase Creature creature = new Creature () ;
public void test () creature.greet () ;
creature.kiss () ;
creature.greet () ;
>
public static void main ( String args []) junit.textui.TestRunner.run ( KissingPrincess2. class ) ;
>
> // /:~
Кроме того, изменения в State везде происходят автоматически, и не требуют редактирования во всех методах класса, чтобы изменения вступили в силу.
Вот основная структура State:
//: state:StateDemo.java
// Simple demonstration of the State pattern.
package state;
interface State void operation1 () ;
void operation2 () ;
void operation3 () ;
>
class ServiceProvider private State state;
public ServiceProvider ( State state ) this .state = state;
>
public void changeState ( State newState ) state = newState;
>
// Передача вызовов методов в реализацию:
public void service1 () // .
state.operation1 () ;
// .
state.operation3 () ;
>
public void service2 () // .
state.operation1 () ;
// .
state.operation2 () ;
>
public void service3 () // .
state.operation3 () ;
// .
state.operation2 () ;
>
>
class Implementation1 implements State public void operation1 () System.out.println ( «Implementation1.operation1()» ) ;
>
public void operation2 () System.out.println ( «Implementation1.operation2()» ) ;
>
public void operation3 () System.out.println ( «Implementation1.operation3()» ) ;
>
>
class Implementation2 implements State public void operation1 () System.out.println ( «Implementation2.operation1()» ) ;
>
public void operation2 () System.out.println ( «Implementation2.operation2()» ) ;
>
public void operation3 () System.out.println ( «Implementation2.operation3()» ) ;
>
>
public class StateDemo extends TestCase static void run ( ServiceProvider sp ) sp.service1 () ;
sp.service2 () ;
sp.service3 () ;
>
ServiceProvider sp = new ServiceProvider ( new Implementation1 ()) ;
public void test () run ( sp ) ;
sp.changeState ( new Implementation2 ()) ;
run ( sp ) ;
>
public static void main ( String args []) junit.textui.TestRunner.run ( StateDemo. class ) ;
>
> // /:~
В функции main( ) вы можете увидеть, что сначала используется первая реализация, замет происходит переключение на вторую реализацию, которая тоже используется.
Существует несколько деталей, из которых вы должны выбрать в соответствии с требованиями вашей собственной реализации, такие как: что фактически вы предоставляете клиенту, использующему Состояние, и как будут сделаны переключения состояния. Иногда (как в свинговом менеджере компоновки) клиент может передать объект напрямую, но в KissingPrincess2.java фактически Состояние использовалось так, что клиент его не видел. Кроме того, механизм изменения состояния может быть простым или сложным — в Машине Состояний, описанной далее в этой книге, есть большое число состояний и рассмотрены различные механизмы переключения.
Свинговый менеджер компоновки, упомянутый выше в качестве примера, — это интересный пример, потому что он показывает поведения и Стратегии (Strategy) и Состояния (State).
Различие между Proxy и Состоянием (State) состоит в проблемах, которые они решают. Proxy чаще всего решает следующие проблемы, как указано в Design Patterns:
- Удаленный прокси. Эти прокси предназначены для объектов, расположенных в разном адресном пространстве. Удаленный прокси создается для вас автоматически при использовании RMI компилятором rmic, когда он создает заглушки и скелеты.
- Виртуальный прокси. Этот прокси обслуживает «ленивую инициализацию» для создания дорогостоящих объектов по требованию.
- Защитный прокси. Используется, когда вы не хотите, чтобы клиентский программист имел полный доступ к проксируемому объекту.
- Умная ссылка. Для добавления дополнительных операций при доступе к проксируемому объекту. Например, для отслеживания и хранения количества ссылок, которые удерживает определенный объект, чтобы реализовать идиому копирование при записи и предотвратить наложение объектов. Более простым примером является отслеживание количества вызовов определенного метода.
Вы можете рассматривать ссылку, как некоторого рода защитный прокси, так как она управляет доступом к реальному объекту в куче (и проверяет, например, что вы не используете null-ссылку).
[Переписать это: В Design Patterns Proxy и State не выглядят связанными друг с другом, потому что два этих шаблона (как я полагаю) даны с различными структурами. State, в частности, использует раздельную иерархию реализаций, а мне кажется, что в этом нет необходимости, до тех пор, пока реализация не выйдет из под вашего контроля (конечно это возможно, но если этот код полностью принадлежит вам, то я не вижу причин не получить выгоду из элегантности и полезности единственного базового класса). Кроме того, Прокси должен использовать тот же базовый класс для своей реализации, что и проксируемый объект, доступ к которому вы контролируете. Независимо от специфики, и Пркси и Состояние являются суррогатами, передающими вызов методов реализуемому объекту.]
Состояние может быть найдено повсеместно, поскольку это достаточно фундаментальная идея. For example, in Builder, the «Director» uses a backend Builder object to produce different behaviors.
Шаблон проектирования состояния в Java – пример учебника
Паттерн состояния является одним из паттернов поведенческого проектирования . Шаблон проектирования состояния используется, когда Объект меняет свое поведение в зависимости от его внутреннего состояния.
Если нам нужно изменить поведение объекта в зависимости от его состояния, мы можем иметь переменную состояния в объекте и использовать блок условия if-else для выполнения различных действий в зависимости от состояния. Шаблон состояния используется для обеспечения систематического и потерянного способа достижения этого посредством реализаций контекста и состояния .
Context — это класс, который имеет ссылку на State для одной из конкретных реализаций State и перенаправляет запрос в объект состояния для обработки. Давайте разберемся с этим на простом примере.
Предположим, что мы хотим реализовать пульт дистанционного управления телевизором с помощью простой кнопки для выполнения действий. Если состояние включено, телевизор включится, а если состояние выключено, телевизор выключится.
Мы можем реализовать это, используя условие if-else, как показано ниже;
Что такое состояние в java
Состояние — паттерн поведения объектов, задающий разную функциональность в зависимости от внутреннего состояния объекта.
Условия, Задача, Назначение
Позволяет объекту варьировать свое поведение в зависимости от внутреннего состояния. Поскольку поведение может меняться совершенно произвольно без каких-либо ограничений, извне создается впечатление, что изменился класс объекта.
Мотивация
Рассмотрим класс TCPConnection, с помощью которого представлено сетевое соединение. Объект этого класса может находиться в одном из нескольких состояний: Established (установлено), Listening (прослушивание), Closed (закрыто). Когда объект TCPConnection получает запросы от других объектов, то в зависимости от текущего состояния он отвечает по-разному. Например, ответ на запрос Open (открыть) зависит от того, находится ли соединение в состоянии Closed или Established. Паттерн состояние описывает, каким образом объект TCPConnection может вести себя по-разному, находясь в различных состояниях.
Основная идея этого паттерна заключается в том, чтобы ввести абстрактный класс TCPState для представления различных состояний соединения. Этот класс объявляет интерфейс, единый для всех классов, описывающих различные рабочие
состояния. В этих подклассах TCPState реализуется поведение, специфичное для конкретного состояния. Например, в классах TCPEstablished и TCPClosed реализовано поведение, характерное для состояний Established и Closed соответственно.
Класс TCPConnection хранит у себя объект состояния (экземпляр подкласса TCPState), представляющий текущее состояние соединения, и делегирует все зависящие от состояния запросы этому объекту. TCPConnection использует свой экземпляр подкласса TCPState достаточно просто: вызывая методы единого интерфейса TCPState, только в зависимости от того какой в данный момент хранится конкретный подкласс TCPState-а – результат получается разным, т.е. в реальности выполняются операции, свойственные только данному состоянию соединения.
А при каждом изменении состояния соединения TCPConnection изменяет свой объект-состояние. Например, когда установленное соединение закрывается, TCPConnection заменяет экземпляр класса TCPEstablished экземпляром TCPClosed.
Признаки применения, использования паттерна Состояние (State)
Используйте паттерн состояние в следующих случаях:
- Когда поведение объекта зависит от его состояния и при этом должно изменяться во время выполнения.
- Когда в коде операций встречаются состоящие из многих ветвей условные операторы, в которых выбор ветви зависит от состояния. Обычно в таком случае состояние представлено перечисляемыми константами. Часто одна и та же структура условного оператора повторяется в нескольких операциях.Паттерн состояние предлагает поместить каждую ветвь в отдельный класс. Это позволяет трактовать состояние объекта как самостоятельный объект, который может изменяться независимо от других.
Решение
Участники паттерна Состояние (State)
- Context (TCPConnection) – контекст.
Определяет единый интерфейс для клиентов.
Хранит экземпляр подкласса ConcreteState, которым определяется текущее состояние. - State (TCPState) – состояние.
Определяет интерфейс для инкапсуляции поведения, ассоциированного с конкретным состоянием контекста Context. - Подклассы ConcreteState (TCPEstablished, TCPListen, TCPClosed) — конкретное состояние.
Каждый подкласс реализует поведение, ассоциированное с некоторым состоянием контекста Context.
Схема использования паттерна Состояние (State)
Класс Context делегирует запросы текущему объекту ConcreteState.
Контекст может передать себя в качестве аргумента объекту State, который будет обрабатывать запрос. Это дает возможность объекту-состоянию (ConcreteState) при необходимости получить доступ к контексту.
Context — это основной интерфейс для клиентов. Клиенты могут конфигурировать контекст объектами состояния State (точнее ConcreteState). Один раз сконфигурировав контекст, клиенты уже не должны напрямую связываться с объектами состояния (только через общий интерфейс State).
При этом либо Context, либо сами подклассы ConcreteState могут решить, при каких условиях и в каком порядке происходит смена состояний.
Вопросы, касающиеся реализации паттерна Состояние (State)
Вопросы, касающиеся реализации паттерна State:
Что определяет переходы между состояниями.
Паттерн состояние ничего не сообщает о том, какой участник определяет условия (критерии) перехода между состояниями. Если критерии зафиксированы, то их можно реализовать непосредственно в классе Context. Однако в общем случае более гибкий и правильный подход заключается в том, чтобы позволить самим подклассам класса State определять следующее состояние и момент перехода. Для этого в класс Context надо добавить интерфейс, позволяющий из объектов State установить его состояние.
Такую децентрализованную логику переходов проще модифицировать и расширять – нужно лишь определить новые подклассы State. Недостаток децентрализации в том, что каждый подкласс State должен «знать» еще хотя бы об одном подклассе другого состояния (на которое собственно он и сможет переключить текущее состояние), что вносит реализационные зависимости между подклассами.
Табличная альтернатива.
Существует еще один способ структурирования кода, управляемого сменой состояний. Это принцип конечного автомата. Он использует таблицу для отображения входных данных на переходы между состояниями. С ее помощью можно определить, в какое состояние нужно перейти при поступлении некоторых входных данных. По существу, тем самым мы заменяем условный код поиском в таблице.
Основное преимущество автомата – в его регулярности: для изменения критериев перехода достаточно модифицировать только данные, а не код. Но есть и недостатки:
— поиск в таблице часто менее эффективен, чем вызов функции,
— представление логики переходов в однородном табличном формате делает критерии менее явными и, стало быть, более сложными для понимания,
— обычно трудно добавить действия, которыми сопровождаются переходы между состояниями. Табличный метод учитывает состояния и переходы между ними, но его необходимо дополнить, чтобы при каждом изменении состоянии можно было выполнять произвольные вычисления.
Главное различие между конечными автоматами на базе таблиц и Паттерн состояние можно сформулировать так: Паттерн состояние моделирует поведение, зависящее от состояния, а табличный метод акцентирует внимание на определении переходов между состояниями.
Создание и уничтожение объектов состояния.
В процессе разработки обычно приходится выбирать между:
— созданием объектов состояния, когда в них возникает необходимость, и уничтожением сразу после использования,
— созданием их заранее и навсегда.
Первый вариант предпочтителен, когда заранее неизвестно, в какие состояния будет попадать система, и контекст изменяет состояние сравнительно редко. При этом мы не создаем объектов, которые никогда не будут использованы, что существенно, если в объектах состояния хранится много информации. Когда изменения состояния происходят часто, поэтому не хотелось бы уничтожать представляющие их объекты (ибо они могут очень скоро понадобиться вновь), следует воспользоваться вторым подходом. Время на создание объектов затрачивается только один раз, в самом начале, а на уничтожение – не затрачивается вовсе. Правда, этот подход может оказаться неудобным, так как в контексте должны храниться ссылки на все состояния, в которые система теоретически может попасть.
Использование динамического изменения.
Варьировать поведение по запросу можно, меняя класс объекта во время выполнения, но в большинстве объектно-ориентированных языков это не поддерживается. Исключение составляет Perl, JavaScript и другие основанные на скриптовом движке языки, которые предоставляют такой механизм и, следовательно, поддерживают Паттерн состояние напрямую. Это позволяет объектам варьировать поведение путем изменения кода своего класса.
Результаты
Результаты использования паттерна состояние:
- Локализует зависящее от состояния поведение.
И делит его на части, соответствующие состояниям. Паттерн состояние помещает все поведение, ассоциированное с конкретным состоянием, в отдельный объект. Поскольку зависящий от состояния код целиком находится в одном из подклассов класса State, то добавлять новые состояния и переходы можно просто путем порождения новых подклассов.
Вместо этого можно было бы использовать данные-члены для определения внутренних состояний, тогда операции объекта Context проверяли бы эти данные. Но в таком случае похожие условные операторы или операторы ветвления были бы разбросаны по всему коду класса Context. При этом добавление нового состояния потребовало бы изменения нескольких операций, что затруднило бы сопровождение. Паттерн состояние позволяет решить эту проблему, но одновременно порождает другую, поскольку поведение для различных состояний оказывается распределенным между несколькими подклассами State. Это увеличивает число классов. Конечно, один класс компактнее, но если состояний много, то такое распределение эффективнее, так как в противном случае пришлось бы иметь дело с громоздкими условными операторами.
Наличие громоздких условных операторов нежелательно, равно как и наличие длинных процедур. Они слишком монолитны, вот почему модификация и расширение кода становится проблемой. Паттерн состояние предлагает более удачный способ структурирования зависящего от состояния кода. Логика, описывающая переходы между состояниями, больше не заключена в монолитные операторы if или switch, а распределена между подклассами State. При инкапсуляции каждого перехода и действия в класс – состояние становится полноценным объектом. Это улучшает структуру кода и проясняет его назначение. - Делает явными переходы между состояниями.
Если объект определяет свое текущее состояние исключительно в терминах внутренних данных, то переходы между состояниями не имеют явного представления; они проявляются лишь как присваивания некоторым переменным. Ввод отдельных объектов для различных состояний делает переходы более явными. Кроме того, объекты State могут защитить контекст Context от рассогласования внутренних переменных, поскольку переходы с точки зрения контекста – это атомарные действия. Для осуществления перехода надо изменить значение только одной переменной (объектной переменной State в классе Context), а не нескольких. - Объекты состояния можно разделять.
Если в объекте состояния State отсутствуют переменные экземпляра, то есть представляемое им состояние кодируется исключительно самим типом, то разные контексты могут разделять один и тот же объект State. Когда состояния разделяются таким образом, они являются, по сути дела, приспособленцами (см. паттерн-приспособленец), у которых нет внутреннего состояния, а есть только поведение.
Пример
Рассмотрим реализацию примера из раздела «Мотивация», т.е. построение некоторой простенькой архитектуры TCP соединения. Это упрощенный вариант протокола TCP, в нем, конечно же, представлен не весь протокол и даже не все состояния TCP-соединений.
Прежде всего определим класс TCPConnection, который предоставляет интерфейс для передачи данных и обрабатывает запросы на изменение состояния: TCPConnection.
В переменной-члене state класса TCPConnection хранится экземпляр класса TCPState. Этот класс дублирует интерфейс изменения состояния, определенный в классе TCPConnection.
TCPConnection делегирует все зависящие от состояния запросы хранимому в state экземпляру TCPState. Кроме того, в классе TCPConnection существует операция ChangeState, с помощью которой в эту переменную можно записать указатель на другой объект TCPState. Конструктор класса TCPConnection инициализирует state указателем на состояние-закрытия TCPClosed (мы определим его ниже).
Каждая операция TCPState принимает экземпляр TCPConnection как параметр, тем самым, позволяя объекту TCPState получить доступ к данным объекта TCPConnection и изменить состояние соединения.
В классе TCPState реализовано поведение по умолчанию для всех делегированных ему запросов. Он может также изменить состояние объекта TCPConnection посредством операции ChangeState. TCPState располагается в том же пакете, что и TCPConnection, поэтому также имеет доступ к этой операции: TCPState.
В подклассах TCPState реализовано поведение, зависящее от состояния. Соединение TCP может находиться во многих состояниях: Established (установлено), Listening (прослушивание), Closed (закрыто) и т.д., и для каждого из них есть свой подкласс TCPState. Для простоты подробно рассмотрим лишь 3 подкласса — TCPEstablished, TCPListen и TCPClosed.
В подклассах TCPState реализуется зависящее от состояния поведение для тех запросов, которые допустимы в этом состоянии.
Поскольку в них нет никакого локального состояния – их можно разделять, т.е. сделать одиночками и иметь только по одному экземпляру каждого класса и создавать обращением к статической операции Instance: TCPEstablished, TCPListen, TCPClosed.
После выполнения специфичных для своего состояния действий эти операции
вызывают ChangeState для изменения состояния объекта TCPConnection. У него же самого нет никакой информации о протоколе TCP. Именно подклассы TCPState определяют переходы между состояниями и действия, диктуемые протоколом.
Известные применения паттерна Состояние (State)
Ральф Джонсон и Джонатан Цвейг характеризуют паттерн состояние и описывают его применительно к протоколу TCP.
Наиболее популярные интерактивные программы рисования предоставляют «инструменты» для выполнения операций прямым манипулированием. Например, инструмент для рисования линий позволяет пользователю щелкнуть в произвольной точке мышью, а затем, перемещая мышь, провести из этой точки линию. Инструмент для выбора позволяет выбирать некоторые фигуры. Обычно все имеющиеся инструменты размещаются в палитре. Работа пользователя заключается в том, чтобы выбрать и применить инструмент, но на самом деле поведение редактора варьируется при смене инструмента: посредством инструмента для рисования мы создаем фигуры, при помощи инструмента выбора — выбираем их и т.д.
Чтобы отразить зависимость поведения редактора от текущего инструмента, можно воспользоваться паттерном состояние.
Можно определить абстрактный класс Tool, подклассы которого реализуют зависящее от инструмента поведение. Графический редактор хранит ссылку на текущий объект Tool и делегирует ему поступающие запросы. При выборе инструмента редактор использует другой объект, что приводит к изменению поведения.
Данная техника используется в каркасах графических редакторов HotDraw и Unidraw. Она позволяет клиентам легко определять новые виды инструментов. В HotDraw класс DrawingController переадресует запросы текущему объекту Tool. В Unidraw соответствующие классы называются Viewer и Tool. На приведенной ниже диаграмме классов схематично представлены интерфейсы классов Tool и DrawingController:
Описанный Джеймсом Коплиеном прием конверт-письмо (EnvelopeLetter) также относится к паттерну состояние. Техника конверт-письмо – это способ изменить класс объекта во время выполнения. Паттерн состояние является частным случаем, в нем акцент делается на работу с объектами, поведение которых зависит от состояния.
Состояние на Java
Состояние — это поведенческий паттерн, позволяющий динамически изменять поведение объекта при смене его состояния.
Поведения, зависящие от состояния, переезжают в отдельные классы. Первоначальный класс хранит ссылку на один из таких объектов-состояний и делегирует ему работу.
Сложность:
Популярность:
Применимость: Паттерн Состояние часто используют в Java для превращения в объекты громоздких стейт-машин, построенных на операторах switch .
Примеры Состояния в стандартных библиотеках Java:
- javax.faces.lifecycle.LifeCycle#execute() (контролируемый из FacesServlet : поведение зависит от текущей фазы (состояния) JSF)
Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Аудиоплеер
Основной класс плеера меняет своё поведение в зависимости от того, в каком состоянии находится проигрывание.
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 Listplaylist = new ArrayList<>(); private int currentTrack = 0; public Player() < this.state = new ReadyState(this); setPlaying(true); for (int i = 1; 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: Снимок работы программы
Состояние на других языках программирования
Купи книгу Погружение в Паттерны и получи архив с десятками детальных примеров, которые можно открывать прямо в IDE.
- Премиум контент
- Книга о паттернах
- Курс по рефакторингу
- Введение в рефакторинг
- Чистый код
- Технический долг
- Когда рефакторить
- Как рефакторить
- Раздувальщики
- Длинный метод
- Большой класс
- Одержимость элементарными типами
- Длинный список параметров
- Группы данных
- Операторы switch
- Временное поле
- Отказ от наследства
- Альтернативные классы с разными интерфейсами
- Расходящиеся модификации
- Стрельба дробью
- Параллельные иерархии наследования
- Комментарии
- Дублирование кода
- Ленивый класс
- Класс данных
- Мёртвый код
- Теоретическая общность
- Завистливые функции
- Неуместная близость
- Цепочка вызовов
- Посредник
- Неполнота библиотечного класса
- Составление методов
- Извлечение метода
- Встраивание метода
- Извлечение переменной
- Встраивание переменной
- Замена переменной вызовом метода
- Расщепление переменной
- Удаление присваиваний параметрам
- Замена метода объектом методов
- Замена алгоритма
- Перемещение метода
- Перемещение поля
- Извлечение класса
- Встраивание класса
- Сокрытие делегирования
- Удаление посредника
- Введение внешнего метода
- Введение локального расширения
- Самоинкапсуляция поля
- Замена простого поля объектом
- Замена значения ссылкой
- Замена ссылки значением
- Замена поля-массива объектом
- Дублирование видимых данных
- Замена однонаправленной связи двунаправленной
- Замена двунаправленной связи однонаправленной
- Замена магического числа символьной константой
- Инкапсуляция поля
- Инкапсуляция коллекции
- Замена кодирования типа классом
- Замена кодирования типа подклассами
- Замена кодирования типа состоянием/стратегией
- Замена подкласса полями
- Разбиение условного оператора
- Объединение условных операторов
- Объединение дублирующихся фрагментов в условных операторах
- Удаление управляющего флага
- Замена вложенных условных операторов граничным оператором
- Замена условного оператора полиморфизмом
- Введение Null-объекта
- Введение проверки утверждения
- Переименование метода
- Добавление параметра
- Удаление параметра
- Разделение запроса и модификатора
- Параметризация метода
- Замена параметра набором специализированных методов
- Передача всего объекта
- Замена параметра вызовом метода
- Замена параметров объектом
- Удаление сеттера
- Сокрытие метода
- Замена конструктора фабричным методом
- Замена кода ошибки исключением
- Замена исключения проверкой условия
- Подъём поля
- Подъём метода
- Подъём тела конструктора
- Спуск метода
- Спуск поля
- Извлечение подкласса
- Извлечение суперкласса
- Извлечение интерфейса
- Свёртывание иерархии
- Создание шаблонного метода
- Замена наследования делегированием
- Замена делегирования наследованием
- Введение в паттерны
- Что такое Паттерн?
- История паттернов
- Зачем знать паттерны?
- Критика паттернов
- Классификация паттернов
- Фабричный метод
- Абстрактная фабрика
- Строитель
- Прототип
- Одиночка
- Адаптер
- Мост
- Компоновщик
- Декоратор
- Фасад
- Легковес
- Заместитель
- Цепочка обязанностей
- Команда
- Итератор
- Посредник
- Снимок
- Наблюдатель
- Состояние
- Стратегия
- Шаблонный метод
- Посетитель
- C#
- C++
- Go
- Java
- PHP
- Python
- Ruby
- Rust
- Swift
- TypeScript