
Тема конечных автоматов (КА) актуальна. Почти как тема реализации светофоров. Но вот, если серьезно, только отношение к ней разное. Время от времени появляются статьи типа «Конечные автоматы (FSM) – это ловушка для программиста» [1]. И здесь очень не хочется, чтобы складывалось превратное представление о некой «псевдо-математической» автоматной абстракции. Нужно оберегать народ от подобных суждений, которые ни на чем не основываются.
Не будем, в пику упомянутой статье, петь дифирамбы автоматам в очередной раз. Потому, как «сколько не говори халва, во рту сладко не станет». Но можно реализовать пример из статьи только с позиции другого взгляда на автоматы. Это больше убедит, чем пространные рассуждение в духе «свист-технологии» или той же многопоточности и конкурентности.
Пусть перед нами стоит задача реализовать светофор и при этом уже есть довольно интересное, если не сказать – оригинальное, решение, которое дает право автору статьи весьма критично высказываться в отношении автоматов. Само решение сводится к следующим шагам: 1) создается таблица, строки которой определяют цвет светофора и его время; 2) создается простая программка перебора строки, которая зажигает в прямом и переносном смыслах, реализуя управление светофором.
Про автоматы в существующем решении ни слова, ни полслова. Оно элементарно и создано, похоже, достаточно быстро, а вот создание стейт-машины, как утверждает автор, было бы долгим и упорным. Поскольку «упираться» автору программы, видимо, совсем не интересно, то попробуем это сделать за него мы.
От программы к автомату
Мы не будем напрягаться, чтобы создать оригинальное решение (тем более, что на нашу реализацию светофора можно посмотреть в [2)], а переведем существующее решение в автоматную форму. Сделаем это с упором на науку, т.е. используя давно известную процедуру преобразования любой блок-схемы алгоритма в конечный автомат. Занимает это буквально минуты. Ниже листинг 1 представляет исходный код программы, а листинг 2 ее автоматный аналог.
Листинг 1. Код исходной программы
tablRec = Table.firstRec();while(true){setLampsState(tablRec.colors);timer = setTimer(tablRec.period);waitFor(timer); tablRec = nextRec(Table);if(tablRec == 0) tablRec = Table.firstRec();}
Листинг 2. Код автоматной программы
LArc TBL_TraffikRukhi7[] = { LArc("s1","s2","--","y1"), // начало. 1-я строка LArc("s2","s3","--","y2y3"), // цвет; таймер LArc("s3","s4","--","y4"), // след строка LArc("s4","s2","x2","y1"), // на 1-ю строку LArc("s4","s2","^x2","--"), // очередная строка таблицы LArc()};int FTraffikRukhi7::x2() { return Table.nCurrentIndex == int(Table.Table.size()); }void FTraffikRukhi7::y1() { tablRec = Table.firstRec(); }void FTraffikRukhi7::y2() { setLampsState(tablRec.colors); }void FTraffikRukhi7::y3() { setTimer(tablRec.period); }void FTraffikRukhi7::y4() { tablRec = Table.nextRec(); }
По факту больше времени у нас занял не автомат, а создание таблицы в стиле исходного решения. Коды созданных классов строки таблицы и самой таблицы приведение на листинге 3.
Листинг 3. Реализация таблицы для светофора
class RLine{public: enum {None, Red, Yellow, Green};public: RLine() {}; RLine(int nC, int nT) { colors = nC; period = nT; } int colors{0}; int period{0}; bool operator==(const RLine &var) const { if (colors!=var.colors) return false; if (period!=var.period) return false; else return true; }};class RTable{public: RTable() {}; void Add(RLine line) { Table.push_back(line); } RLine firstRec() { nCurrentIndex = 0; return Table[0]; } RLine nextRec() { int nn = int(Table.size()); int nIndex = nCurrentIndex; if (nIndex>=nn) { nCurrentIndex= 0; return RLine(0, 0); } nCurrentIndex++; return Table[nIndex]; } vector<RLine> Table; int nCurrentIndex{0};};
Код светофора демонстрирует листинг 4, где он приведен без кода автомата, который рассмотрен ранее (см. листинге 2).
Листинг 4. Код светофора, исключая автомат
#include "lfsaappl.h"#include "RTable.h"extern LArc TBL_TraffikRukhi7[];class FTraffikRukhi7 : public LFsaAppl{public: bool FCreationOfLinksForVariables(); FTraffikRukhi7(string strNam, LArc *pTBL=TBL_TraffikRukhi7); virtual ~FTraffikRukhi7(void); CVar *pVarRed; CVar *pVarYellow; CVar *pVarGreen; CVar *pVarStrInitColor; RTable Table; RLine tablRec; void setLampsState(int clr); void setTimer(int tmr); int timer;protected: int x2(); void y1(); void y2(); void y3(); void y4();};#include "stdafx.h"#include "FTraffikRukhi7.h"FTraffikRukhi7::FTraffikRukhi7(string strNam, LArc *pTBL): LFsaAppl(pTBL, strNam, nullptr){ Table.Add(RLine(RLine::Red, 20)); Table.Add(RLine(RLine::Yellow, 5)); Table.Add(RLine(RLine::Green, 17)); Table.Add(RLine(RLine::None, 1)); Table.Add(RLine(RLine::Green, 1)); Table.Add(RLine(RLine::None, 1)); Table.Add(RLine(RLine::Green, 1)); Table.Add(RLine(RLine::None, 1)); Table.Add(RLine(RLine::Green, 1)); Table.Add(RLine(RLine::None, 1)); Table.Add(RLine(RLine::Green, 1));}FTraffikRukhi7::~FTraffikRukhi7(void){}bool FTraffikRukhi7::FCreationOfLinksForVariables() { pVarRed = CreateLocVar("bRed", CLocVar::vtBool, ""); pVarRed->bSerial = true; pVarYellow = CreateLocVar("bYellow", CLocVar::vtBool, ""); pVarYellow->bSerial = true; pVarGreen = CreateLocVar("bGreen", CLocVar::vtBool, ""); pVarGreen->bSerial = true; pVarStrInitColor = CreateLocVar("nInitColor", CLocVar::vtString, "", true, "Red"); return true;}void FTraffikRukhi7::setLampsState(int clr) { switch(clr) { case RLine::Red: pVarRed->SetDataSrc(nullptr, 1.0); pVarYellow->SetDataSrc(nullptr, 0.0); pVarGreen->SetDataSrc(nullptr, 0.0); break; case RLine::Yellow: pVarRed->SetDataSrc(nullptr, 0.0); pVarYellow->SetDataSrc(nullptr, 1.0); pVarGreen->SetDataSrc(nullptr, 0.0); break; case RLine::Green: pVarRed->SetDataSrc(nullptr, 0.0); pVarYellow->SetDataSrc(nullptr, 0.0); pVarGreen->SetDataSrc(nullptr, 1.0); break; default: pVarRed->SetDataSrc(nullptr, 0.0); pVarYellow->SetDataSrc(nullptr, 0.0); pVarGreen->SetDataSrc(nullptr, 0.0); break; }}void FTraffikRukhi7::setTimer(int tmr) { FCreateDelay(tmr*1000); }
Мы не будем рассматривать подключение процесса светофора к автоматному ядру, т.к. это не сложно, стандартно и занимает буквально десяток строк. Да и не очень интересно с точки зрения теории КА и работы приложения. Сама форма диалога и работа светофора представлены на гиф 1.
Заключение
Конечные автоматы – ловушка для дилетанта, но инструмент профессионала. Инструмент удобный и эффективный. Безусловно, для этого нужно уверенно владеть моделью КА, а в идеале иметь представление о возможностях теории автоматов. Хотя бы ее начал. Только в этом случае технология автоматного программирования станет вашим повседневным инструментом. Инструментом полноценным и по-настоящему эффективным.
Думаю, сказанных слов в пользу КА в этот раз вполне достаточно. Но если возникнет желание вникнуть в тему детальнее, то начните хотя бы с работы [3].
Литература
1. Конечный автомат (FSM) – ловушка для программиста. https://habr.com/ru/articles/1044244/
2. Но почему, почему, почему был светофор зеленый? https://habr.com/ru/articles/1044514/
3. Автоматная модель управления программ. https://habr.com/ru/articles/484588/
ссылка на оригинал статьи https://habr.com/ru/articles/1045614/