Ваши классы StateA
, StateB
не имеют элементов данных. Предположительно, другие состояния также не будут иметь изменяемых элементов данных, поскольку, если бы они имели их, то это состояние было бы странным образом распределено между различными экземплярами A, то есть разными конечными автоматами, работающими одновременно.
Таким образом, ваши синглтоны избежали половины проблемы с шаблоном (глобальное изменяемое состояние). Фактически, с небольшими изменениями в вашем дизайне, вы можете заменить классы состояний на функции; замените указатели на их экземпляры указателями на функции; и замените виртуальный вызов на action
вызовом через указатель текущей функции. Если кто-то доставляет вам много хлопот за использование синглетонов, но вы уверены, что ваш дизайн верен, вы можете внести это незначительное изменение и посмотреть, заметят ли они, что их «исправление» вообще не имело существенного значения для дизайна.
Другая половина проблемы с синглетонами все еще не будет решена, хотя это фиксированные зависимости. С вашими одиночками невозможно смоделировать StateB, чтобы протестировать StateA изолированно, или ввести гибкость, когда вы хотите ввести в вашу библиотеку новый конечный автомат, который совпадает с текущим, за исключением того, что StateA вместо этого переходит к StateC государстваB. Вы можете или не можете считать это проблемой. Если вы это сделаете, то вместо того, чтобы делать каждое состояние единичным, вам нужно сделать его более настраиваемым.
Например, вы могли бы дать каждому состоянию некоторый идентификатор (строку или, возможно, член перечисления), и для каждого идентификатора зарегистрировать State*
где-нибудь в классе А. Затем, вместо того, чтобы переключаться на одноэлементный экземпляр StateB, StateA может перейти к любому объекту состояния, используемому для представления «состояния B» в этом автомате . Это может быть испытательным макетом для определенных случаев. Вы по-прежнему вызываете new
один раз для каждого состояния для каждой машины, но не один раз для каждого изменения состояния.
По сути, это все еще шаблон стратегии для класса A, как и в вашем проекте. Но вместо того, чтобы иметь единую стратегию для продвижения конечного автомата и его постоянной замены по мере изменения состояния, у нас есть одна стратегия для каждого состояния, через которое проходит машина, все с одним и тем же интерфейсом. Другой вариант в C ++, который будет работать для некоторых целей, но не для других, - это использовать (форму) основанного на политике проектирования вместо стратегий. Затем каждое состояние обрабатывается классом (предоставляется в качестве аргумента шаблона), а не объектом (устанавливается во время выполнения). Поведение вашего конечного автомата, таким образом, фиксируется во время компиляции (как в вашем текущем проекте), но может быть настроено путем изменения аргументов шаблона, а не каким-либо образом изменяя или заменяя класс StateB. Тогда вам вообще не нужно вызывать new
- создайте отдельный экземпляр каждого состояния в конечном автомате в качестве члена данных, используйте указатель на один из них для представления текущего состояния и сделайте виртуальный вызов это как раньше. При разработке на основе политик обычно не нужны виртуальные вызовы, поскольку обычно отдельные политики полностью независимы, тогда как здесь они реализуют общий интерфейс, и мы выбираем между ними во время выполнения.
Все это предполагает, что A знает о конечном множестве состояний. Это может быть нереально (например, A может представлять собой универсальный программируемый конечный автомат, который должен принимать произвольное количество произвольных состояний). В этом случае вам нужен способ создания ваших состояний: сначала создайте экземпляр StateA и экземпляр StateB. Поскольку каждое состояние имеет один выходной путь, каждый объект состояния должен иметь один элемент данных, который является указателем на новое состояние. Таким образом, создав состояния, установите для экземпляров StateA «следующее состояние» для экземпляра StateB и наоборот. Наконец, установите элемент данных текущего состояния A для экземпляра StateA и запустите его. Обратите внимание, что когда вы делаете это, вы создаете циклический график зависимостей, поэтому, чтобы избежать утечек памяти, вам, возможно, придется принять специальные меры по обработке ресурсов помимо подсчета ссылок.