Тема конечных автоматов (КА) актуальна. Почти как тема реализации светофоров. Но вот, если серьезно, только отношение к ней разное. Время от времени появляются статьи типа «Конечные автоматы (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.

Gif 1. Работа светофора
Gif 1. Работа светофора

Заключение

Конечные автоматы – ловушка для дилетанта, но инструмент профессионала. Инструмент удобный и эффективный. Безусловно, для этого нужно уверенно владеть моделью КА, а в идеале иметь представление о возможностях теории автоматов. Хотя бы ее начал. Только в этом случае технология автоматного программирования станет вашим повседневным инструментом. Инструментом полноценным и по-настоящему эффективным.

Думаю, сказанных слов в пользу КА в этот раз вполне достаточно. Но если возникнет желание вникнуть в тему детальнее, то начните хотя бы с работы [3].

Литература

1.       Конечный автомат (FSM) – ловушка для программиста.   https://habr.com/ru/articles/1044244/

2.       Но почему, почему, почему был светофор зеленый? https://habr.com/ru/articles/1044514/

3.       Автоматная модель управления программ. https://habr.com/ru/articles/484588/

Комментарии (0)