Состояние - это поведенческий паттерн, позволяющий динамически изменять поведение объекта при смене его состояния.
Поведения, зависящие от состояния, переезжают в отдельные классы. Первоначальный класс хранит ссылку на один из таких объектов-состояний и делегирует ему работу.
Сложность:
Популярность:
Применимость: Паттерн Состояние часто используют в Java для превращения в объекты громоздких стейт-машин, построенных на операторах switch .
Примеры Состояния в стандартных библиотеках Java:
Признаки применения паттерна: Методы класса делегируют работу одному вложенному объекту.
Основной класс плеера меняет своё поведение в зависимости от того, в каком состоянии находится проигрывание.
Поведение объекта зависит от его состояния и должно изменяться во время выполнения программы. Такую схему можно реализовать, применив множество условных операторов: на основе анализа текущего состояния объекта предпринимаются определенные действия. Однако при большом числе состояний условные операторы будут разбросаны по всему коду, и такую программу будет трудно поддерживать.
Паттерн State решает указанную проблему следующим образом:
Паттерн State не определяет, где именно определяется условие перехода в новое состояние. Существует два варианта: класс Context или подклассы State . Преимущество последнего варианта заключается в простоте добавления новых производных классов. Недостаток заключается в том, что каждый подкласс State для осуществления перехода в новое состояние должен знать о своих соседях, что вводит зависимости между подклассами.
Существует также альтернативный таблично-ориентированный подход к проектированию конечных автоматов, основанный на использовании таблицы однозначного отображения входных данных на переходы между состояниями. Однако этот подход обладает недостатками: трудно добавить выполнение действий при выполнении переходов. Подход, основанный на использовании паттерна State, для осуществления переходов между состояниями использует код (вместо структур данных), поэтому эти действия легко добавляемы.
Класс Context определяет внешний интерфейс для клиентов и хранит внутри себя ссылку на текущее состояние объекта State . Интерфейс абстрактного базового класса State повторяет интерфейс Context за исключением одного дополнительного параметра - указателя на экземпляр Context . Производные от State классы определяют поведение, специфичное для конкретного состояния. Класс "обертка" Context делегирует все полученные запросы объекту "текущее состояние", который может использовать полученный дополнительный параметр для доступа к экземпляру Context .
Паттерн State позволяет объекту изменять свое поведение в зависимости от внутреннего состояния. Похожая картина может наблюдаться в работе торгового автомата. Автоматы могут иметь различные состояния в зависимости от наличия товаров, суммы полученных монет, возможности размена денег и т.д. После того как покупатель выбрал и оплатил товар, возможны следующие ситуации (состояния):
Рассмотрим пример конечного автомата с двумя возможными состояниями и двумя событиями.
#include
В режиме государственного (государственного образца), поведение класса основывается на его статус изменился. Этот тип шаблонов проектирования относятся поведенческие модели.
В государственной модели, мы создаем объекты и различные поведенческие состояний наряду с состоянием объекта изменяется измененном представления контекста объекта.
Намерение: Позволяет объекту изменять свое поведение во внутреннем состоянии изменяется, то объект появляется, чтобы изменить свой класс.
Главным образом решить: поведение объекта зависит от его состояния (атрибутов), и вы можете изменить его в соответствии с его государством, связанные с изменением поведения.
Когда использовать: Код содержит большое количество объектов, связанных со статусом условных операторов.
Как исправить: статус конкретных классов абстрактных вне.
Ключевой код: режим командного интерфейса, как правило, только один метод. Статус интерфейса, в котором один или несколько методов. Кроме того, метод государственного режима класса реализации, как правило, возвращаемого значения, или изменить значение переменной экземпляра. То есть, состояние и состояние объектной модели, как правило, актуальны. Методы класса реализации имеют различные функции, методы интерфейса охвачены. Режим Состояние и командный режим то же самое может быть использован для устранения других условий, если... Else выбор.
Примеры применения: 1, играть в баскетбол игрок может иметь нормальное состояние, а не нормальное состояние и ненормальное состояние. 2, маркиза Yi Цзэн колокола, то "часы абстрактный интерфейс", "часы А" и другие конкретные состояния, специфическая среда "" китайские колокола (контекст).
Преимущества: 1, инкапсулирует правила преобразования. 2, перечислить возможные состояния, прежде чем перечисление государству необходимо определить статус видов. 3, все с поведением государства, связанных в класс, и вы можете легко добавить новое состояние, нужно только изменить состояние объекта может изменить поведение объектов. 4, что позволяет осуществить переход состояния логическое состояние объекта в одном, а не один огромный блок условных операторов. 5, позволяет использовать несколько объектов разделяют среду состояние объекта, тем самым уменьшая количество объектов в системе.
Недостатки: 1, Паттерн состояние использования связано с увеличением числа системных классов и объектов. 2, структура и реализация государственной формы являются более сложными, при неправильном использовании может вызвать путаницу структуру программы и код. 3, поддержка государственного образца "Открытый Закрытый принцип" не очень хорошо, вы можете переключать состояние государственной модели, добавляя новые классы нужно изменить статус лиц, ответственных за переходы состояний исходного кода, или не может перейти в новое состояние, и изменить состояние класс действовать также необходимо модифицировать исходный код соответствующего класса.
Сценарии использования: 1, с поведением изменения состояния и изменения сцены. 2, условный переход утверждение заменой.
Примечание: При использовании ограниченного государственного поведения путем государственного режима, а государство не более пяти.
Мы создадим интерфейс статуса и сущностигосударственный класс реализации интерфейсаState. Контекст представляет собой класс с определенным состоянием.
StatePatternDemo, мы демонстрируем использование объектовКонтекст Контекст класса и статуса, чтобы продемонстрировать изменение поведения в состоянии изменения.
Создайте интерфейс.
State.java
Public interface State { public void doAction(Context context); }
Создать класс сущностей, который реализует интерфейс.
StartState.java
Открытый класс StartState реализует государство { общественного недействительными DoAction (контекст Контекст) { System.out.println ("Игрок находится в стартовом состоянии"); context.setState (это); } общественного Строка ToString () { вернуть "начальное состояние"; } }
StopState.java
Public class StopState implements State { public void doAction(Context context) { System.out.println("Player is in stop state"); context.setState(this); } public String toString(){ return "Stop State"; } }
Создание классаконтекста.
Context.java
Открытый класс {Context частное Государственное государство; общественный контекст () { состояние = NULL; } общественного недействительными SetState (Государственное государство) { this.state = состояние; } общенародного государства GetState () { возвращать состояние; } }
Используйтеконтекст , чтобы увидеть поведение при изменении состояния измененийсостояния.
StatePatternDemo.java
Открытый класс StatePatternDemo { государственной статической силы основных (String ) {агдз Контекст Контекст = новый контекст (); StartState startState = новый StartState (); startState.doAction (контекст); System.out.println (context.getState () ToString ().); StopState stopState = новый StopState (); stopState.doAction (контекст); System.out.println (context.getState () ToString ().); } }
Проверьте выход.
Игрок находится в стартовом состоянии Start государственный Игрок находится в состоянии останова остановленном
Позволяет объекту варьировать свое поведение в зависимости от внутреннего состояния. Извне создается впечатление, что изменился класс объекта.
Паттерн «Состояние» предполагает выделение базового класса или интерфейса для всех допустимых операций и наследника для каждого возможного состояния
Когда поведение объекта должно зависеть от его состояния и может изменяться динамически во время выполнения
Когда в коде методов объекта используются многочисленные условные конструкции, выбор которых зависит от текущего состояния объекта
15.02.2016
21:30
Паттерн Состояние (State) предназначен для проектирования классов, которые имеют несколько независимых логических состояний. Давайте сразу перейдем к рассмотрению примера.
Допустим, мы разрабатываем класс управления веб-камерой. Камера может находиться в трех Состояниях:
Поскольку мы работаем с паттером Состояние, то лучше всего начать с изображения Диаграммы состояний:
Теперь превратим эту диаграмму в код. Чтобы не усложнять реализацию, код работы с веб-камерами мы опускаем. При необходимости вы сами можете добавить соответствующие вызовы библиотечных функций.
Сразу привожу полный листинг с минимальными комментариями. Далее мы обсудим ключевые детали этой реализации подробнее.
#include
Обращаю внимание на макрос DECLARE_GET_INSTANCE . Конечно, использование макросов в C++ не поощряется. Однако это относится к случаям, когда макрос выступает в роли аналога шаблонной функции. В этом случае всегда отдавайте предпочтение последним.
В нашем случае макрос предназначен для определения статической функции, необходимой для реализации . Поэтому его использование можно считать оправданным. Ведь оно позволяет сократить дублирование кода и не представляет каких-либо серьезных угроз.
Классы-Состояния мы объявляем в главном классе - WebCamera . Для краткости я использовал inline -определения функций-членов всех классов. Однако в реальных приложениях лучше следовать рекомендациям о разделении объявления и реализации по h и cpp файлам.
Классы Состояний объявлены внутри WebCamera для того, чтобы они имели доступ к закрытым полям этого класса. Конечно, это создает крайне жесткую связь между всеми этими классами. Но Состояния оказываются настолько специфичными, что об их повторном использовании в других контекстах не может быть и речи.
Основу иерархии классов состояний образует абстрактный класс WebCamera::State:
Class State { public: virtual ~State() { } virtual void connect(WebCamera*) { throw NotSupported(); } virtual void disconnect(WebCamera* cam) { std::cout << "Деинициализируем камеру..." << std::endl; // ... cam->changeState(NotConnectedState::getInstance()); } virtual void start(WebCamera*) { throw NotSupported(); } virtual void stop(WebCamera*) { throw NotSupported(); } virtual Frame getFrame(WebCamera*) { throw NotSupported(); } protected: State() { } };
Все его функции-члены соответствуют функциям самого класса WebCamera . Происходит непосредственное делегирование:
Class WebCamera { // ... void connect() { m_state->connect(this); } void disconnect() { m_state->disconnect(this); } void start() { m_state->start(this); } void stop() { m_state->stop(this); } Frame getFrame() { return m_state->getFrame(this); } // ... State* m_state; }
Ключевой особенностью является то, что объект Состояния принимает указатель на вызывающий его экземпляр WebCamera . Это позволяет иметь всего три объекта Состояний для сколь угодно большого числа камер. Достигается такая возможность за счет использования паттерна Синглтон. Конечно, в рамках примера существенного выигрыша вы от этого не получите. Но знать такой прием все равно полезно.
Сам по себе класс WebCamera не делает практически ничего. Он полностью зависит от своих Состояний. А эти Состояния, в свою очередь, определяют условия выполнения операций и обеспечивают нужный контекст.
Большинство функций-членов WebCamera::State выбрасывают наше собственное WebCamera::NotSupported . Это вполне уместное поведение по умолчанию. Например, если кто-то попытается инициализировать камеру, когда она уже инициализирована, то вполне закономерно получит исключение.
При этом для WebCamera::State::disconnect() мы предусматриваем реализацию по умолчанию. Такое поведение подойдет для двух состояний из трех. В результате мы предотвращаем дублирование кода.
Для смены состояния предназначена закрытая функция-член WebCamera::changeState() :
Void changeState(State* newState) { m_state = newState; }
Теперь к реализации конкретных Состояний. Для WebCamera::NotConnectedState достаточно переопределить операции connect() и disconnect() :
Class NotConnectedState: public State { public: DECLARE_GET_INSTANCE(NotConnectedState) void connect(WebCamera* cam) { std::cout << "Инициализируем камеру..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); } void disconnect(WebCamera*) { throw NotSupported(); } private: NotConnectedState() { } };
Для каждого Состояния можно создать единственный экземпляр. Это нам гарантирует объявление закрытого конструктора.
Другим важным элементом представленной реализации является то, что в новое Состояние мы переходим лишь в случае успеха. Например, если во время инициализации камеры произойдет сбой, то в Состояние ReadyState переходить рано. Главная мысль - полное соответствие фактического состояния камеры (в нашем случае) и объекта-Состояния.
Итак, камера готова к работе. Заведем соответствующий класс Состояния WebCamera::ReadyState:
Class ReadyState: public State { public: DECLARE_GET_INSTANCE(ReadyState) void start(WebCamera* cam) { std::cout << "Запускаем видео-поток..." << std::endl; // ... cam->changeState(ActiveState::getInstance()); } private: ReadyState() { } };
Из Состояния готовности мы можем попасть в активное Состояние захвата кадров. Для этого предусмотрена операция start() , которую мы и реализовали.
Наконец мы дошли до последнего логического Состояния работы камеры WebCamera::ActiveState:
Class ActiveState: public State { public: DECLARE_GET_INSTANCE(ActiveState) void stop(WebCamera* cam) { std::cout << "Останавливаем видео-поток..." << std::endl; // ... cam->changeState(ReadyState::getInstance()); } Frame getFrame(WebCamera*) { std::cout << "Получаем текущий кадр..." << std::endl; // ... return "Current frame"; } private: ActiveState() { } };
В этом Состоянии можно прервать захват кадров с помощью stop() . В результате мы попадем обратно в Состояние WebCamera::ReadyState . Кроме того, мы можем получать кадры, которые накапливаются в буфере камеры. Для простоты под "кадром" мы понимаем обычную строку. В реальности это будет некоторый байтовый массив.
А теперь мы можем записать типичный пример работы с нашим классом WebCamera:
Int main() { WebCamera cam(0); try { // cam в Состоянии NotConnectedState cam.connect(); // cam в Состоянии ReadyState cam.start(); // cam в Состоянии ActiveState std::cout << cam.getFrame() << std::endl; cam.stop(); // Можно было сразу вызвать disconnect() // cam в Состоянии ReadyState cam.disconnect(); // cam в Состоянии NotConnectedState } catch(const WebCamera::NotSupported& e) { // Обрабатываем исключение } catch(...) { // Обрабатываем исключение } return 0; }
Вот что в результате будет выведено на консоль:
Инициализируем камеру... Запускаем видео-поток... Получаем текущий кадр... Current frame Останавливаем видео-поток... Деинициализируем камеру...
А теперь попробуем спровоцировать ошибку. Вызовем connect() два раза подряд:
Int main() { WebCamera cam(0); try { // cam в Состоянии NotConnectedState cam.connect(); // cam в Состоянии ReadyState // Но для этого Состояния операция connect() не предусмотрена! cam.connect(); // Выбрасывает исключение NotSupported } catch(const WebCamera::NotSupported& e) { std::cout << "Произошло исключение!!!" << std::endl; // ... } catch(...) { // Обрабатываем исключение } return 0; }
Вот что из этого получится:
Инициализируем камеру... Произошло исключение!!! Деинициализируем камеру...
Обратите внимание, что камера все же была деинициализирована. Вызов disconnect() произошел в деструкторе WebCamera . Т.е. внутреннее Состояние объекта осталось абсолютно корректным.
С помощью паттерна Состояние вы можете однозначно преобразовать Диаграмму состояний в код. На первый взгляд реализация получилась многословной. Однако мы пришли к четкому делению по возможным контекстам работы с основным классом WebCamera . В результате при написании каждого отдельного Состояния мы смогли сконцентрироваться на узкой задаче. А это лучший способ написать ясный, понятный и надежный код.