Refaktoring IoT do maszyny stanów

Kilka tygodni temu wspominałem o potrzebie refaktoringu aplikacji IsTableBusy. Planowałem użycie wzorca projektowego Command i Active Object. Jednak po przemyśleniach, bliżej kodu, doszedłem do wniosku, że w tym wypadku lepsze będzie użycie maszyny stanów. Poniżej przedstawiam uzasadnienie dlaczego tak oraz fragmenty kodu.
Dlaczego nie Active Object
Początkowo ta idea wydawała się być zacna i bardzo obiecująca. Jednak każdy refaktoring staram się zacząć od analizy, aby mieć pewność, że wiem co chcę osiągnąć i do jakiego punktu zmierzam. Pisałem już o tym w pierwszej poście Jak refaktorować. Po chwili refleksji i wysileniu zwojów mózgowych, gdy już miałem przed oczami ewentualną strukturę kodu. Doszedłem do wniosku, że użycie wzorca Active Object przewyższy złożoność całego, na razie dość prostego, programu.
Stany IsTableBusy
Na chwilę obecną urządzenie do IsTableBusy może znajdować się w jednym z pięciu stanów:
- Initializing – łączenie z Wifi i ogólna konfiguracja początkowa
- Registering – zarejestrowanie włączenia urządzenia i zapalenie diody symbolizującej aktualny stan
- WaitingForClick – sprawdzenie, czy został kliknięty przycisk, jeżeli tak to zmiana stanu na któryś z dwóch następnych
- UpdatingStatusToBusy – strzał do API z informacją, że stół jest zajęty i zaświecenie czerwonej diody
- UpdatingStatusToFree – strzał do API z informacją, że stół jest wolny i zaświecenie zielonej diody
Co ostatecznie przekonało mnie, że należy wybrać prostsze rozwiązanie, bo stanów nie ma wiele, a przejścia miedzy nimi są nieskomplikowane.
Main
Moim celem było przede wszystkim uproszczenie pliku Main.cpp. Zatem po zmianach, w metodzie setup() odbywa się inicjalizacja potrzebnych obiektów wraz z pobraniem danych z konfiguracji. Natomiast w metodzie loop() powiadamiam StateHandler, żeby obsłużył aktualny stan.
StateHandler *stateHandler;
void setup()
{
Serial.begin(9600);
Button *button = new Button(BUTTON_TOP);
Light *green = new Light(GREEN_LED);
Light *red = new Light(RED_LED);
Configuration *configuration = new Configuration();
WifiConnector *wifiConnector = new WifiConnector();
wifiConnector->AddConnectionData(configuration->GetWifiSsid(), configuration->GetWifiPassword());
UrlPreparer *urlPreparer = new UrlPreparer();
char *url = urlPreparer->PrepareUrl();
ApiClient *apiClient = new ApiClient(url);
stateHandler = new StateHandler(button, green, red, wifiConnector, apiClient);
}
void loop()
{
stateHandler->handle();
}
Dzięki takiemu rozwiązaniu kod jest prosty, przejrzysty i wiadomo co robi i dlaczego. Pozostaje mi tylko odpowiezieć do znajduje się w klasie StateHandler.
StateHandler
Jest to byt, który jest odpowiedzialny za wykonanie odpowiedniej akcji dla bieżącego stanu oraz na zmianę stanu na nowy. Dzięki takiemu zabiegowi udało mi się wyeliminować wszystkie pętle z kodu, a wraz z tym znacznie uprościć zarządzanie przepływem w aplikacji. Jej główny fragment prezentuje się następująco:
void StateHandler::handle()
{
switch (currentState)
{
case States::Initializing:
{
this->initialize();
break;
}
case States::Registering:
{
this->registerDevice();
break;
}
case States::WaitingForClick:
{
this->checkClick();
break;
}
case States::UpdatingStatusToBusy:
{
this->setIsBusy(true);
break;
}
case States::UpdatingStatusToFree:
{
this->setIsBusy(false);
break;
}
}
}
Zakończenie
Uważam, że po tych zmianach kod stał się już całkiem przyjemny. Jakie jest Twoje zdanie? Czy widzisz jeszcze jakieś ciekawe opcje na lepszą wersję tego kodu? A może też się zastanawiasz nad jakimś refaktorem, ale się wahasz? W obu przypadkach koniecznie daj znać. Czekam ;).
0 Komentarzy