Мне часто приходится реализовывать объект, способный переключать свое поведение в ответ на команду пользователя. Например, это может быть случай, когда устройство представления класса подключено к ПК и управляется пользователем через графический интерфейс. В более общем смысле, устройство должно жить самостоятельно, со своим собственным расписанием операций.
Поскольку я хотел бы «извлечь» это поведение из определенного класса устройств, чтобы улучшить повторное использование кода, здесь я предлагаю шаблонный класс конечных автоматов, использующий Qt. Я также привел пример использования в классе А. Что вы (более опытные программисты, чем я 🙂 думаете об этом? Это «правильный» способ создать такой класс? Есть ли проблемы с производительностью?
template < class Base,
typename T,
class ThreadPolicy>
class FSM
{
public:
typedef bool (Base::*my_func)();
struct SState {
SState(){}
SState(const T& id_arg,
const T& next_arg,
const T& error_arg,
const QList<T>& branches_arg,
const my_func& op_arg) :
id(id_arg),
next(next_arg),
error(error_arg),
branches(branches_arg),
op(op_arg)
{}
T id; // state ID
T next; // next state
T error; // in case of error
QList<T> branches; // allowed state switching from current
my_func op; // operation associated with current state
};
typedef QMap<T ,SState> SMap;
bool switchState(const T& ns){
return _checkAllowed(ns);
}
bool addState(const T& id, const SState& s){
return _register(id, s);
}
protected:
void _loop(Base* ptr){
if ((ptr->*m_states[m_state].op)()) {
ThreadPolicy::Lock();
if(m_externalSwitch){
m_externalSwitch = false;
ThreadPolicy::Unlock();
return;
}
m_state = m_states[m_state].next;
ThreadPolicy::Unlock();
} else {
ThreadPolicy::Lock();
if(m_externalSwitch){
m_externalSwitch = false;
ThreadPolicy::Unlock();
return;
}
m_state = m_states[m_state].error;
ThreadPolicy::Unlock();
}
}
bool _checkAllowed(const T& cmd){
if (!m_states[m_state].branches.contains(cmd)) { return false;}
ThreadPolicy::Lock();
m_state = cmd;
m_externalSwitch = true;
ThreadPolicy::Unlock();
return true;
}
bool _register(const SState& s){
if(m_states.find(s.id) != m_states.end()) { return false; } // state with same ID already exist
m_states[s.id] = s; // add the new state to the map
return true;
}
SMap m_states; // map states to Baseclass methods
T m_state; // holds my current state
bool m_externalSwitch; // check if user request a state switch
};
class A :
public QObject,
public FSM< A, QString, MultiThreaded >
{
Q_OBJECT
A(){
// SState startState; myState.branches << "start" << "stop";
_register(SState("start",
"start",
"stop",QStringList(("start","stop")),
&A::_doStart));
_register(SState("stop",
"stop",
"stop",QStringList(("stop","start")),
&A::_doStop));
}
private slots:
void run(){
for(;;){
_loop(this);
QCoreApplication::processEvents();
}
}
private:
bool _doStart(){ return true;}
bool _doStop(){ return true;}
};
A. Что вы (более опытные программисты, чем я 🙂 думаете о
тот? Это «правильный» способ создать такой класс? Здесь
проблемы с производительностью ?
ХОРОШО! Я грубо посмотрел на ваш дизайн, и мне не очень-то хорошо от фреймворка общего назначения FSM. Это слишком узко, чтобы его можно было использовать в расширенном контексте. Некоторые точки критики:
В общем, я бы рекомендовал следовать GoF State Pattern для реализации FSM. Для очень простых диаграмм состояний switch(event)
case <event>: changeState(newState)
может быть достаточно. Но отображение событий в виде записей методов FSM и их передача текущему экземпляру класса состояния делает всю конструкцию гораздо более гибкой. Подумайте о необязательных параметрах, которые идут вместе с конкретным событием, и вам нужно будет расширить дизайн конечного автомата для них.
В целом ваш подход к использованию CRTP для вашего конечного автомата это хорошая идея, но для того, что вы продемонстрировали, простой динамический полиморфизм (с использованием виртуальных функций-членов) также подойдет.
Что касается проблем с производительностью, не думайте, что вы столкнетесь с проблемами в вашей текущей среде, но это полностью зависит от того, где и в каком контексте вы хотите развернуть.
Я хочу порекомендовать вам взглянуть на мою платформу шаблонов State Machine Class. STTCL, который предоставляет различные аспекты C ++, основанные на шаблонах, для конечных автоматов, совместимых с UML 2.0, в соответствии с уже упомянутым шаблоном состояния GoF.
если это все еще актуально, я реализовал конечный автомат в C ++, который использует Object OP, его довольно просто использовать, и если вы посмотрите на main.cpp, то есть пример.
Код здесь и сейчас он скомпилирован как библиотека.
Дайте мне знать, если это то, что вы хотите!
Ура,
Andrea