Паттерн Состояние на C++

Паттерн Состояние на C++

Паттерн Состояние (State) предназначен для проектирования классов, которые имеют несколько независимых логических состояний. Давайте сразу перейдем к рассмотрению примера.

Допустим, мы разрабатываем класс управления веб-камерой. Камера может находиться в трех Состояниях:

  1. Не инициализирована. Назовем NotConnectedState;
  2. Инициализирована и готова к работе, но кадры еще не захватываются. Пусть это будет ReadyState;
  3. Активный режим захвата кадров. Обозначим ActiveState.

Поскольку мы работаем с паттером Состояние, то лучше всего начать с изображения Диаграммы состояний:

изображение-01

Теперь превратим эту диаграмму в код. Чтобы не усложнять реализацию, код работы с веб-камерами мы опускаем. При необходимости вы сами можете добавить соответствующие вызовы библиотечных функций.

Сразу привожу полный листинг с минимальными комментариями. Далее мы обсудим ключевые детали этой реализации подробнее.

Обращаю внимание на макрос DECLARE_GET_INSTANCE. Конечно, использование макросов в C++ не поощряется. Однако это относится к случаям, когда макрос выступает в роли аналога шаблонной функции. В этом случае всегда отдавайте предпочтение последним.

В нашем случае макрос предназначен для определения статической функции, необходимой для реализации паттерна Синглтон. Поэтому его использование можно считать оправданным. Ведь оно позволяет сократить дублирование кода и не представляет каких-либо серьезных угроз.

Классы-Состояния мы объявляем в главном классе — WebCamera. Для краткости я использовал inline-определения функций-членов всех классов. Однако в реальных приложениях лучше следовать рекомендациям о разделении объявления и реализации по h и cpp файлам.

Классы Состояний объявлены внутри WebCamera для того, чтобы они имели доступ к закрытым полям этого класса. Конечно, это создает крайне жесткую связь между всеми этими классами. Но Состояния оказываются настолько специфичными, что об их повторном использовании в других контекстах не может быть и речи.

Основу иерархии классов состояний образует абстрактный класс WebCamera::State:

Все его функции-члены соответствуют функциям самого класса WebCamera. Происходит непосредственное делегирование:

Ключевой особенностью является то, что объект Состояния принимает указатель на вызывающий его экземпляр WebCamera. Это позволяет иметь всего три объекта Состояний для сколь угодно большого числа камер. Достигается такая возможность за счет использования паттерна Синглтон. Конечно, в рамках примера существенного выигрыша вы от этого не получите. Но знать такой прием все равно полезно.

Сам по себе класс WebCamera не делает практически ничего. Он полностью зависит от своих Состояний. А эти Состояния, в свою очередь, определяют условия выполнения операций и обеспечивают нужный контекст.

Большинство функций-членов WebCamera::State выбрасывают наше собственное исключение WebCamera::NotSupported. Это вполне уместное поведение по умолчанию. Например, если кто-то попытается инициализировать камеру, когда она уже инициализирована, то вполне закономерно получит исключение.

При этом для WebCamera::State::disconnect() мы предусматриваем реализацию по умолчанию. Такое поведение подойдет для двух состояний из трех. В результате мы предотвращаем дублирование кода.

Для смены состояния предназначена закрытая функция-член WebCamera::changeState():

Теперь к реализации конкретных Состояний. Для WebCamera::NotConnectedState достаточно переопределить операции connect() и disconnect():

Для каждого Состояния можно создать единственный экземпляр. Это нам гарантирует объявление закрытого конструктора.

Другим важным элементом представленной реализации является то, что в новое Состояние мы переходим лишь в случае успеха. Например, если во время инициализации камеры произойдет сбой, то в Состояние ReadyState переходить рано. Главная мысль — полное соответствие фактического состояния камеры (в нашем случае) и объекта-Состояния.

Итак, камера готова к работе. Заведем соответствующий класс Состояния WebCamera::ReadyState:

Из Состояния готовности мы можем попасть в активное Состояние захвата кадров. Для этого предусмотрена операция start(), которую мы и реализовали.

Наконец мы дошли до последнего логического Состояния работы камеры WebCamera::ActiveState:

В этом Состоянии можно прервать захват кадров с помощью stop(). В результате мы попадем обратно в Состояние WebCamera::ReadyState. Кроме того, мы можем получать кадры, которые накапливаются в буфере камеры. Для простоты под «кадром» мы понимаем обычную строку. В реальности это будет некоторый байтовый массив.

А теперь мы можем записать типичный пример работы с нашим классом WebCamera:

Вот что в результате будет выведено на консоль:

А теперь попробуем спровоцировать ошибку. Вызовем connect() два раза подряд:

Вот что из этого получится:

Обратите внимание, что камера все же была деинициализирована. Вызов disconnect() произошел в деструкторе WebCamera. Т.е. внутреннее Состояние объекта осталось абсолютно корректным.

Выводы

С помощью паттерна Состояние вы можете однозначно преобразовать Диаграмму состояний в код. На первый взгляд реализация получилась многословной. Однако мы пришли к четкому делению по возможным контекстам работы с основным классом WebCamera. В результате при написании каждого отдельного Состояния мы смогли сконцентрироваться на узкой задаче. А это лучший способ написать ясный, понятный и надежный код.

Понравилась статья? Поделиться с друзьями:
Добавить комментарий