У меня есть немного проблемы, которая включает моделирование конечного автомата.
Мне удалось сделать определенную инженерию знаний и 'перепроектировать' ряд примитивных детерминированных правил, которые определяют, а также изменения состояния состояния.
Я хотел бы знать то, что расценивают лучшие практики:
Как строго протестировать мои состояния и изменения состояния, чтобы удостовериться, что система не может закончиться в неопределенном состоянии.
Как осуществить требования изменения состояния (например, должно быть невозможно пойти непосредственно от stateFoo до StateFooBar, т.е. пропитать каждое состояние со 'знанием' о состояниях, к которым это может перейти.
Идеально, я хотел бы использовать чистый, основанный на шаблоне дизайн с шаблонами по мере возможности.
Я действительно должен где-нибудь запустить, хотя и я был бы благодарен за любые указатели (никакая предназначенная игра слов), которые отправляются мой путь.
Обязательно ознакомьтесь с библиотекой Boost Statechart Library .
Тестирование не имеет ничего общего с шаблонами, шаблонами и т. Д. Я бы порекомендовал среду тестирования, такую как CppUnit (часть семейства xUnit), для сбора всех ваших тестовых случаев. Количество, конечно, будет зависеть от сложности конечного автомата.
Ваш вопрос о принудительном переходе между состояниями лежит в основе дизайна вашего класса для вашего конечного автомата. Я бы сказал, что у состояния будет набор дочерних состояний, в которые оно может перейти, а также событие, которое будет запускать каждое из них. Если событие Foo не имеет дочернего элемента FooBar, то к нему невозможно перейти.
Я бы погуглил "объектно-ориентированные конечные машины", чтобы получить некоторые дизайнерские идеи.
Когда я думал о подобных проблемах, я думал, что шаблон проектирования Composite может быть его частью, потому что состояние может представлять более сложный конечный автомат. У меня был бы интерфейс состояния с реализациями SimpleState и CompositeState. Придется начать все сначала и посмотреть, все ли получится.
Использование конечных автоматов - это то, что время от времени возникает. Я обычно делаю так, как предлагал ravenspoint, и просто делаю оператор switch. Но это работает, только если состояния не слишком велики. Это похоже на ваш случай. Принимая это во внимание, я думаю, что лучше всего начать с хорошей архитектуры, которая позволит вам выполнять некоторые из ваших задач. Я принял предложение Даффимо и попробовал Google. Эта статья выглядела интересно - Объектно-ориентированные конечные машины . Это может быть излишним, но я думаю, что это даст основу, которую можно было бы легко протестировать с помощью чего-то вроде CppUnit.
Некоторые другие полезные ссылки из поиска Google
Если вы ищете классический шаблон конечного автомата шаблонов проектирования GOF, посмотрите википедию .
Взгляните на этой странице (на момент написания) примера Java.
Он имеет класс StateContext
, который, как вы можете видеть из примера использования, имеет клиентов, которые знают о методе writeName
. Реализация такова: this.myState.writeName (this, name);
, что означает, что он перенаправляет вызов в текущее состояние, передавая себя в качестве первого аргумента.
Теперь посмотрите на Состояние интерфейса
, у него есть метод writeName
, который соответствует описанному выше использованию. Если вы посмотрите и на StateA
, и на StateB
, они обратятся к контексту, устанавливающему новое состояние.
Это большая часть Государственного Паттерна. Единственное, что нужно понимать, это то, что класс StateContext
может содержать все данные, участвующие в его работе, включая ссылку (в C ++ это должен быть указатель) на текущее состояние. Все состояния в совокупности содержат все поведение, но нет данных, вместо этого данные (плюс вспомогательные методы) откладываются в контексте.
Когда я разрабатываю конечный автомат (я обычно использую TDD), я не утруждаюсь тестированием переходов между состояниями, просто конечное поведение такое, как я хочу.
Черт возьми, это не так сложно, как кажется. Код конечного автомата очень простой и короткий.
Сохраните состояние в переменной, скажем myState.
Конечный автомат будет оператором switch, переходящим по значению переменной myState, чтобы выполнить код для каждого состояния.
Код будет полон таких строк:
myState = newState;
Чтобы обеспечить соблюдение требований к переходу между состояниями, вам нужно добавить вместо этого небольшой метод, например этот
void DoSafeStateTransition( int newState )
{
// check myState -. newState is not forbidden
// lots of ways to do this
// perhaps nested switch statement
switch( myState ) {
…
case X: switch( newState )
case A: case B: case Z: HorribleError( newState );
break;
...
}
// check that newState is not undetermined
switch( newState ) {
// all the determined states
case A: case B: case C … case Z: myState = newState; break;
default: HorribleError( newState );
}
}
void HorribleError( int newState )
{ printf("Attempt to go from %d to %d - disallowed\n",
myState, newState );
exit(1);
}
Я предлагаю, чтобы этот простой и достаточно короткий, чтобы инспектирование сделало это лучше, чем модульное тестирование - оно, безусловно, будет намного быстрее!
На мой взгляд, суть модульного тестирования заключается в том, что тестовый код проще, чем тестируемый, поэтому его легче проверить на правильность, а затем использовать для тестирования сложного кода. Часто проще проверить код конечного автомата, чем тестовый код конечного автомата. Нет особого смысла сообщать о прохождении 100% модульного теста, если вы плохо понимаете, правильны ли модульные тесты.
Другими словами: написать конечный автомат легко, а создать правильный - сложно. Модульные тесты скажут вам только, правильно ли вы написали дизайн, а не правильно ли он был.
Похоже на безупречное приложение для модульного тестирования. Существует множество фреймворков для модульного тестирования. Мне нравится Boost .