LINUX.ORG.RU

Конечный автомат, велосипед

 , ,


0

4

Добрый день. В целях самообразования решил запилить очередной велосипед. Имеется конечный автомат (F), событие (E), текущее состояние конечного автомата (S), менеджер, принимающий события (M)

Пока что получается следущее взаимодействие:

  • некоторая сущность кладёт в M событие E
  • M по своим правилам (их можно опустить) посылает E в F
  • F «пересылает» E своему текущему S
  • S соответствующим образом обрабатывает E, после чего должен сообщить F в каком состоянии он должен остаться/перейти после того как обработает E.

Возникает вопрос по последнему пункту: есть ли паттерны/механизмы «подписки/регистрации» в базовом классе?

По идее я могу сделать так:

  • Создать класс FSM под конкретную задачу
  • Отнаследоваться от FSM_I
  • Внутри FSM описать классы под каждые состояния и сделать по одному экземпляру
  • Хранить адрес текущего состояния и изменять его

Но такой механизм мне не очень нравится, хочется решить как-то более красиво, наверняка такие способы есть.

#include <iostream>
#include <string>

class StateI
{
public:
	virtual ~StateI() = default;
	virtual StateI* proceed() = 0;
	virtual std::string getName() = 0;
};

class FsmI
{
public:
	virtual ~FsmI() = default;
	virtual void proceed() = 0;
};

class Fsm
		: public FsmI
{
public:
	Fsm();
	void proceed() override;

private:
	class StateA : public StateI
	{
	public:
		StateI* proceed() override;
		std::string getName() override;
	private:
		int m_nCounter{0};
	};

	class StateB : public StateI
	{
	public:
		StateI* proceed() override;
		std::string getName() override;
	};

	static StateA m_stateA;
	static StateB m_stateB;
	StateI* m_currentState;
};

Fsm::StateA Fsm::m_stateA = Fsm::StateA();
Fsm::StateB Fsm::m_stateB = Fsm::StateB();

Fsm::Fsm()
{
	m_currentState = &m_stateA;
}

void Fsm::proceed()
{
	StateI* before = m_currentState;
	StateI* after = m_currentState->proceed();
	m_currentState = after;
	std::cout << before->getName() << " -> " << after->getName() << std::endl;
}

StateI* Fsm::StateA::proceed()
{
	++m_nCounter;
	if (m_nCounter > 2)
		return &m_stateB;
	return this;
}

std::string Fsm::StateA::getName()
{
	return "stateA";
}

StateI* Fsm::StateB::proceed()
{
	return &m_stateA;
}

std::string Fsm::StateB::getName()
{
	return "stateB";
}

int main(int argc, char** argv)
{
	std::cout << std::endl << "---- f1 ----" << std::endl;
	Fsm f1;
	f1.proceed();
	f1.proceed();
	f1.proceed();
	f1.proceed();
	return 0;
}

Получаю следующий вывод:

---- f1 ----
stateA -> stateA
stateA -> stateA
stateA -> stateB
stateB -> stateA
Понятное дело что нормально работать будет только 1 экземпляр FSM, но это можно допилить (т.к. пример писался на коленке). В целом мысль думаю понятна.

Юзать можно что угодно (либу собираю с поддержкой C++17), но не готовые либы, хочется самому набить шишки (потом пойдёт многопоточность и т.п.).

Нафига это вообще надо, классы какие-то, наследования? Это все можно сделать на банальных goto или через switch

SZT ★★★★★ ()
Последнее исправление: SZT (всего исправлений: 1)

IFSM должен выглядеть примерно так:

template<class O, class S, class I>
class FsmI
{
public:
	virtual ~FsmI() = default;
        virtual S getCurrentState() const = 0
        virtual O handle(I input) = 0;
};

Если нужно чтобы оно еще и быстро работало, то нужен handle вида:

virtual std::vector<O> handle(std::vector<I> input) = 0;

Отдельный класс для состояния не нужен. Лучше что-то типа enum внутри класса в секции public объявить.

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

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

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

Чаушеску^WАлександреску смотрит на тебя с укоризной :)

Deleted ()

на каком ты этаже работаешь?

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

Отдельный класс для состояния не нужен.

А что если у меня будет очень сложная fsm? Получится один очень большой метод, не уверен насколько просто будет читать такой метод, который будет обрабатывать все состояния.

А если я захочу добавить ещё много разных «побочных» методов состоянию?

penetrator3000 ()

как же любят в Протее усложнять простые конструкции...

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

Сделай сколько хочешь методов, а потом заводи их логику внутрь handle. Так ты сможешь унифицировать работу с fsm.

Norgat ★★★★★ ()

В целях самообразования решил запилить очередной велосипед.

В целях самообразования не велосипеды пилят, а изучают существующие реализации (в Boost есть очень навороченная реализация). Потому что пиление велосипедов имеет одну неприятную особенность - начинаешь считать свои велосипеды самыми лучшими, несмотря на треугольные колеса.

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

А тут я никакого повышения удобства не вижу

Речь не про «тут», а про то, что ты хочешь обходиться goto и switch.

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

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

Такие велосипеды не поедут, что ограничивает самолюбование. Приходится допиливать колёса хотя бы до квадратного состояния.

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

Суть не меняется. Начинаешь думать, что твой велосипед лучше готовых проектов. А в один прекрасный момент он скажет «кря» и дальше не поедет

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

это самый быстрый и проверенный практикой способ реализации. несмотря на вопли местных «знатоков». отлично работает. и не надо плодить сущности без необходимости.

на самом деле, для машины состояний нужен ещё объект. объектов, которые меняют состояние, может быть несколько (от двух до овердохрена). так что имеет смысл вводить понятие списка объектов машины состояний.

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

считать свои велосипеды самыми лучшими

не думаю что так будет

Так будет, пока ты не сравнишь свой велосипед с существующими. Но если ты начнешь с ознакомления с существующими, ты сэкономишь себе время. Так что смотри Boost и https://hoverbear.org/2016/10/12/rust-state-machine-pattern/ (это Rust, но принципы применимы и к C++).

Инженерная разработка начинается с анализа существующих решений; обучение, в общем, тоже. А из велосипедистов-самоучек получаются SZT и Iron_Bug, которые могут кое-что, но уверены, что могут всё.

tailgunner ★★★★★ ()
#include <cstdio>

struct iStateControl {
  virtual ~iStateControl() {}
  virtual void set_state_1() = 0;
  virtual void set_state_2() = 0;
};

struct iState {
  virtual ~iState() {}
  virtual void state_action(iStateControl &) = 0;
};

struct State1 : iState {
  void state_action(iStateControl &c) override {puts("state1"); c.set_state_2();}
};

struct State2 : iState {
  void state_action(iStateControl &c) override {puts("state2"); c.set_state_1();}
};

struct Fsm : iStateControl {
  State1 state1;
  State2 state2;
  iState *state;
  Fsm() : state(&state1) {}
  void set_state_1() override {state = &state1;}
  void set_state_2() override {state = &state2;}

  void on_event() {
    state->state_action(*this);
  }
};

int main()
{
  Fsm fsm;
  fsm.on_event();
  fsm.on_event();
}
anonymous ()
Ответ на: комментарий от Iron_Bug

несмотря на вопли местных «знатоков». отлично работает. и не надо плодить сущности без необходимости.

обкакаешся тесты для своего «проверенного практикой способа» писать. а без тестов код как бы: нет писал тестов - ну мужик.

anonymous ()

Ragel compiles executable finite state machines from regular languages. Ragel targets C, C++ and ASM.

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

Нафига это вообще надо, классы какие-то, наследования?

спроси у алана кея.

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

а если число состояний намного больше двух?

я таких не встречал Лол :-)

если число состояний больше двух, то спп сосёт, мой друг. нужен язык с variant types and pattern maching

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

А из велосипедистов-самоучек получаются SZT и Iron_Bug, которые могут кое-что, но уверены, что могут всё.

Пока ты делаешь только то, что можешь сделать, практически ты ничем не отличаешься от них ;)

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

Пока ты делаешь только то, что можешь сделать, практически ты >ничем не отличаешься от них ;)

Кажется любой человек может сделать только то что может сделать.
Так что любой человек, товарищи, ни чем не отличается от них.
   В.И. Ленин, собр. соч., т. 100500, стр. 9000
anonymous ()
Ответ на: комментарий от anonymous

мы так писали автоматизацию Гознака. всё работает. а какаются только тутошние онанимусы. да, ещё педе-rust'ы появились: у них недержание памяти в софте и им нужны памперсы, чтобы ничего не текло.

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

Тут чото попахивает толи undo stack толи pattern observer толи и тем и другим.

deep-purple ★★★★★ ()

Функторами вида:

state f(input)

И map<state, f>

Вызов f возвращает следующий стейт.

Как-то так.

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

В целях самообразования не велосипеды пилят, а изучают существующие реализации (в Boost есть очень навороченная реализация).

считать свои велосипеды самыми лучшими

не думаю что так будет

Так будет, пока ты не сравнишь свой велосипед с существующими. Но если ты начнешь с ознакомления с существующими, ты сэкономишь себе время. Так что смотри Boost и https://hoverbear.org/2016/10/12/rust-state-machine-pattern/

Очень и очень спорный тезис.

У автора задача не уровня продакшена, а уровня институтской лабораторной работы по информатике. Такие лабы нужно делать как раз самим, никуда не подглядывая. Конечно, когда код уже написан и работает, нужно показать его преподавателю и узнать, почему он говно — но не раньше. В отсутствии преподавателя — сравнить своё решение с другими, пусть даже с Boost. И дело тут не в экономии времени.

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

Такие велосипеды не поедут, что ограничивает самолюбование.

Кмк вот уж что не проблема то не проблема: в коде который написан «для себя» запускается и делает пару переходов = работает. Так что самолюбование таким способом имхо тут ограничить невозможно. Единственный вариант, как указал тейлганнер - изучать хорошие решения.

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

В качестве антитезиса всё таки: код надо писать, никуда не денешься потому как разобраться в коде уровня буст новичку невозможно. После того как напишешь хоть какой то прототип - начинаешь понимать проблемы гораздо глубже.
Конечно, если у тебя овердофига лет программирования, то ты можешь позволить себе (наверное, я например, не могу по большому счёту) изучать лучшие решения без написания кода. Но когда опыта мало тут по моему без вариантов.

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

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

Не читал твой ответ. Я указывал на то, что треугольные колеса в принципе не позволят ехать. Если есть понимание законов механики, с моим утверждением нельзя не согласиться. А значит, у велосипедов должен быть какой-то минимальный уровень качества.

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

Не читал твой ответ.

оригинально.

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

Я же указал тебе что аналогия всего лишь аналогия и качество ПО которое пишется для себя может не сильно отличаться от велосипеда с треугольными колёсами. Если возвращаться к аналогии с велосипедом, то для «домашних» программ достаточно того чтобы «он лежал как велосипед» или «стоял как велосипед» или просто «выглядел как велосипед если с ним сфоткаться по пояс». А на эти моменты форма колёс особо не влияет, поэтому подойдут и треугольные.

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

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

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