LINUX.ORG.RU

Повторение выполнения блока кода

 ,


0

2

Добрый день, прошу совета, как можно реализовать подобную штуку: Есть некое устройство, с которым общается программа, скажем по COM порту. На это устройство в разный момент времени, в зависимости от текущего состояния, должны быть отправлены разные команды, устройство их получает и должно ответить на них. Есть некоторые состояния, когда последовательно можно отправить сразу несколько команд. Так же, нужно предусмотреть повторную отправку команд для текущего состояния, если ответа не было или был не корректный. 1. Для каждого состояния есть свой метод, в котором отправляются команды (если для конкретного состояния ожидается сразу последовательно несколько команд, то в этом методе сразу же и отправляем последовательно эти команды). 2. Дальше если пришел ответ на команду/команды и он корректный, то переходим в следующее состояние, если ответа нет, то повторяем текущий шаг. Вот и вопрос в том как лучше реализовать повторный вызов метода для текущего состояния? Пока сделал таким образом: Если был таймаут ожидания (время ожидания ответа истекло) или некорректный ответ, то с помощью switch/case в зависимости от текущего состояния дергаем соответствующий ему метод. Но решение с этими switch/case кажется каким-то громоздким, кто как реализовывал подобные решения?

ИМХО, вам нужно смотреть на то, как конечные автоматы реализуются в C++. Например, в рамках Boost-а: Statechart, Meta State Machine, Boost-experimental.SML. Или вне Boost-а: TinyFSM.

Так же можно глянуть, как подобные вещи решаются, скажем, в готовых акторных фреймворках для C++: QP/C++ и SObjectizer.

eao197 ★★★★★ ()

switch/case может быть медленным, а громоздким ты его не делай, вызов функции в case и все. Вообще в таких штуках лучше не мудрить, пишу в конце действия что-то типа NextStep=STEP_KILL_ALL и switch по NextStep.

ilovewindows ★★★★★ ()

Тебе правильно советуют - используй конечный автомат, он же state machine.

решение с этими switch/case кажется каким-то громоздким

man таблица переходов

no-such-file ★★★★★ ()

Я не знаю за конечные автоматы, наверное это хорошее решение и стоит пользоваться им.

Но судя по описанию проблемы совсем непонятно, чем неустраивает нечто подобное:

enum class States { success, state1, state2 };

void updateDevice(States& state) {
    // Вызываем нужные действия. Если успешно, то меняем state на States::success или другое нужное состояние, иначе оставляем без изменений.
    cout << "Succesfully updated from state " << (int)state << " to state " << (int)States::success << endl;
    state = States::success;
}

int main() {
    States state = States::state1;
    while(state != States::success) {
        switch(state) {
            case States::state1:
                updateDevice(state);
                break;
            case States::state2:
                cout << "Unimplemented!" << endl;
                state = States::success;
                break;
            case States::success:
            default:
                break;
        }
    }
}

Если различных состояний много и не хочется писать switch, то можно попробовать разрулить это всё шаблонами по тэгу. Ну или наследованием с полиморфизмом.

Ivan_qrt ★★★★★ ()

ну когда поймешь все детали сможешь switch переделать в chain responsibility какой-та, да только вот в крестах проблемы с реализацией даже тривиальных паттернов, потому разжевывать тебе сейчас все не собираюсь, дам лишь пару псевдо-примеров для размышления (не кресты, динамика все поля):

# дано
# state - некоторое глобальное состояние
# action - параметры вызываемого действия

твой подход (контрольный):

if action.type == "withdraw":
  if state.balance > action.data.amount:
    apply_withdrawal(state, action)
  else:
    raise InsufficientBalance(state, action)

if action.type == "deposit":
  if state.user.is_active:
    show_cashier()
  else:
    raise InactiveAccount(state.user)

че-то такое тут, не столь важно, возможные пути рефакторинга:

def apply_action_alpha(state, action):
  # ...

def apply_action_beta(state, action):
  # ...

def apply_action_third(state, action):
  # ...

routes = { # таблица "маршрутизации" управлением выполнения в зависимости от типа действия (расширяемый иф-кейс, как-раз твой случай)
  "deposit": apply_action_first,
  "withdrawal": apply_action_second,
  "activation": apply_action_third,
  "nop": lambda state, action: raise Exception("NOP")
}

def apply_action(state, action):
  handle = action.type in routes and routes[action.type] or routes["nop"]
  return handle(state, action)

вариант chain responsibility:

def action1(state, action):
  if action.type == "type1":
    state["value1"] = get_type1_result(action)

def action2(state, action):
  if action.type == "type2":
    state["value2"] = get_type2_value(action)

actions = [action1, action2]

def dispatch(state, action):
  for action in actions:
    action(state, action) # здесь действие само решает по состоянию и объекту события, ответственно ли оно за обработку запроса или нет

задолбался писать, короче начни думать и гулить паттерны и расширяемость

anonymous ()
class state
{
public:
  virtual ~state();

  virtual void on_enter();
  virtual void on_exit();
  virtual void on_recv_frame();
  virtual void on_t0_timeout();
  virtual void on_t1_timeout();
};

class state1: public state
{
/* custom code */
};

class state2: public state
{
/* custom code */
};

class machine
{
  //Many, many states 
  state1 s1;
  state2 s2;
  state3 s3;
  state4 s4;
  state5 s5;
  state6 s6;

  state* s_curr;

  void init()
  {
     s_curr=&s1;
  }

  inline void switch_state(state* s)
  {
    s_curr->on_exit();
    s_curr=s;
    s_curr->on_enter();
  }

  //Events
  void on_recv_frame()
  {
    s_curr->on_recv_frame();
  }
  void on_t0_timeout()
  {
    s_curr->on_t0_timeout();
  }
  void on_t1_timeout()
  {
    s_curr->on_t1_timeout();
  }
};
pathfinder ★★★ ()
Ответ на: комментарий от pathfinder

хм...интересно. У меня есть объект, который обеспечивает взаимодействие с этим внешним устройством, банально, есть метода sendData, readData. Не будет ли плохим тоном передавать указатель на этот объект в конструктор класса состояния? Чтобы когда мы переходим в новое состояние в методе «on_enter» вызывался метод sendData?

hell_wood ()
Ответ на: комментарий от hell_wood

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

anonymous ()

Конечные автоматы из говна и палок - это очень печально. Если хочется изобретать велосипеды, по крайней мере смотрите на существующие аналоги.

tailgunner ★★★★★ ()
Ответ на: комментарий от anonymous

так как менять состояния должны те же самые состоягния , занть про машину им необходимо

Это не проблема, просто не хотелось загромождать код.

class machine;

class state
{
private:
  machine* owner;
public:
  state(machine* _owner)
    :owner(_owner)
  {
  }
/* ... */
};

class state1 : public state
{
public:
   state1(machine* _owner)
     :state(_owner)
   {
   }
};


class machine
{
public:
  state1 s1;
  state1 s2;
  state1 s3;
  state1 s4;

  machine()
    :s1(this),
     s2(this),
     s3(this),
     s4(this)
  {
  }
};

pathfinder ★★★ ()
Ответ на: комментарий от hell_wood

Не будет ли плохим тоном передавать указатель на этот объект в конструктор класса состояния?

Может я не знаю каких-то деталей, но в общем случае я не вижу проблем.

pathfinder ★★★ ()
Ответ на: комментарий от tailgunner

Конечные автоматы из говна и палок - это очень печально. Если хочется изобретать велосипеды, по крайней мере смотрите на существующие аналоги.

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

pathfinder ★★★ ()
Последнее исправление: pathfinder (всего исправлений: 1)
Ответ на: комментарий от pathfinder

Конечные автоматы из говна и палок - это очень печально. Если хочется изобретать велосипеды, по крайней мере смотрите на существующие аналоги.

Случаи бывают разные

Какие бы случаи не были - разработка всегда должна начинаться с изучения существующих аналогов. Всегда. Это азбука.

tailgunner ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.