DEV Community

Somenath Mukhopadhyay
Somenath Mukhopadhyay

Posted on • Edited on

The State Design pattern in C++ using timer and notification

Guru

As a #guru, let me today pay my tributes to my #guru in the tech world who has influenced my technical attributes and mindset.

Although I have never seen them, neither they know I exist.

But I am grateful to them - and because of them I have at last become an engineer in the true sense.

First guru: The authors of The Gang of Four Design Pattern Book

Second Guru: Linus Torvalds for offering Linux and rather helping me come out of the vicious cycle of windows

Third Guru: The Android creator and Google for showing me the different nitty-gritty of a complicated framework that i learnt through delving into the Android framework code

Fourth Guru: A professor of USA University by the name Doug from whom i got insights of how to study properly the Android OS difficult parts of the code like Asynctask Framework and he showed me the right approach of taking the OS study from en engineer’s perspective - delving into difficult concurrent framework of the Android OS

And my all-time Guru : My family for giving me a sweet, caring, loving #home and my only son, #ridit, for becoming my best student from his early childhood. He is still driving me to burn the midnight oil in front of the laptop to explore the deep ocean of Computer science.

And I am sure, the way the whole professional world of technology is moving, very soon somebody will have to dismiss the book called

theworldisflat - The World Is Flat

and write a new one

theflatistheworld - The Flat Is The World

Now let's come back to the subject. Let's discuss the technical side of the State Design Pattern.

The state design pattern is a powerful tool in software design, allowing you to encapsulate the behavior of an object based on its internal state.

The main participants in this design pattern are:

Context : This object represents the entire entity experiencing state changes. It holds a reference to the current state object and exposes an interface for external interaction.

State : These are concrete classes representing each possible state the object can be in. Each state class defines its own behavior for responding to events and transitions.

Transitions : These define how the object can move from one state to another. They can be triggered by events, user actions, or internal conditions.

In my example, I have cited the process of cooking chicken - how the chicken states alter from an uncleaned state to a cleaned state to a marinated state to a cooked state and finally served to the guests.

Here's the class diagram...

Class Diagram of the State Pattern

Runtime Behaviour

The runtime interaction follows these steps:

  • The application creates the Context.
  • The Context delegates execution to the current State.
  • The State starts a Timer.
  • The Timer expires.
  • The Timer invokes the notification callback.
  • The callback requests the Context to change its current State.
  • The Context delegates subsequent operations to the new State.

The source code is as follows:

/*
 * Callback.h
 *
 * Created on: May 5, 2022
 * Author: som
 */

#ifndef CALLBACK_H_
#define CALLBACK_H_

class Callback {
public:
    virtual void goToNextStateFromCallback() = 0;
    virtual ~Callback() = default;
};

#endif
Enter fullscreen mode Exit fullscreen mode

#ifndef CHICKENSTATE_H_
#define CHICKENSTATE_H_

#include <string>

class ChickenState {
public:
    virtual ~ChickenState() = default;
    virtual void wash(int period, int totalTime) {}
    virtual void marinate(int period, int totalTime) {}
    virtual void cook(int period, int totalTime) {}
    virtual void serve(int period, int totalTime) {}
    virtual void goToNextState() = 0;
    virtual std::string getTag() const = 0;
};

#endif

Enter fullscreen mode Exit fullscreen mode

#ifndef UNCLEANEDSTATE_H_
#define UNCLEANEDSTATE_H_

#include "ChickenState.h"
#include "Mamma.h"
#include "CleanedState.h"
#include <iostream>

class UncleanedState : public ChickenState {
private:
    Mamma* mamma;
    std::string tag = "Uncleaned";

public:
    UncleanedState(Mamma* ctx) : mamma(ctx) {}

    void wash(int period, int totalTime) override {
        std::cout << "[Uncleaned] Chicken is getting washed... please wait...\n";
        mamma->t.setTimeout([this]() {
            mamma->goToNextStateFromCallback();
        }, totalTime);
    }

    void goToNextState() override {
        mamma->isUncleanedStateDone = true;
        mamma->changeChickenState(std::make_unique<CleanedState>(mamma));
    }

    std::string getTag() const override { return tag; }
};

#endif
Enter fullscreen mode Exit fullscreen mode

#ifndef CLEANEDSTATE_H_
#define CLEANEDSTATE_H_

#include "ChickenState.h"
#include "Mamma.h"
#include "MarinatedState.h"
#include <iostream>

class CleanedState : public ChickenState {
private:
    Mamma* mamma;
    std::string tag = "Cleaned";

public:
    CleanedState(Mamma* ctx) : mamma(ctx) {}

    void marinate(int period, int totalTime) override {
        std::cout << "[Cleaned] Chicken is getting marinated... please wait...\n";
        mamma->t.setTimeout([this]() {
            mamma->goToNextStateFromCallback();
        }, totalTime);
    }

    void goToNextState() override {
        mamma->isCleanedStateDone = true;
        mamma->changeChickenState(std::make_unique<MarinatedState>(mamma));
    }

    std::string getTag() const override { return tag; }
};

#endif

Enter fullscreen mode Exit fullscreen mode

#ifndef MARINATEDSTATE_H_
#define MARINATEDSTATE_H_

#include "ChickenState.h"
#include "Mamma.h"
#include "CookedState.h"
#include <iostream>

class MarinatedState : public ChickenState {
private:
    Mamma* mamma;
    std::string tag = "Marinated";

public:
    MarinatedState(Mamma* ctx) : mamma(ctx) {}

    void cook(int period, int totalTime) override {
        std::cout << "[Marinated] Chicken is getting cooked... please wait...\n";
        mamma->t.setTimeout([this]() {
            mamma->goToNextStateFromCallback();
        }, totalTime);
    }

    void goToNextState() override {
        mamma->isMarinatedStateDone = true;
        mamma->changeChickenState(std::make_unique<CookedState>(mamma));
    }

    std::string getTag() const override { return tag; }
};

#endif

Enter fullscreen mode Exit fullscreen mode

#ifndef COOKEDSTATE_H_
#define COOKEDSTATE_H_

#include "ChickenState.h"
#include "Mamma.h"
#include <iostream>

class CookedState : public ChickenState {
private:
    Mamma* mamma;
    std::string tag = "Cooked";

public:
    CookedState(Mamma* ctx) : mamma(ctx) {}

    void serve(int period, int totalTime) override {
        std::cout << "[Cooked] Chicken is ready! Enjoy the delicious Ajwaini Chicken!\n";
        mamma->isCookedStateDone = true;
        mamma->timerRunning = false; // Break the execution loop
    }

    void goToNextState() override {}
    std::string getTag() const override { return tag; }
};

#endif

Enter fullscreen mode Exit fullscreen mode

#ifndef MAMMA_H_
#define MAMMA_H_

#include <memory>
#include <atomic>
#include "Callback.h"
#include "ChickenState.h"
#include "timer.h"

class Mamma : public Callback {
private:
    std::unique_ptr<ChickenState> currentState;

public:
    Timer t;
    std::atomic<bool> timerRunning{true};

    // Flags tracking state milestones safely
    std::atomic<bool> isUncleanedStateDone{false};
    std::atomic<bool> isCleanedStateDone{false};
    std::atomic<bool> isMarinatedStateDone{false};
    std::atomic<bool> isCookedStateDone{false};

    Mamma();
    ~Mamma() override = default;

    void changeChickenState(std::unique_ptr<ChickenState> nextState);
    void startPreparation();
    void goToNextStateFromCallback() override;

    ChickenState* getCurrentState() { return currentState.get(); }
};

#endif

Enter fullscreen mode Exit fullscreen mode

/*
 * Mamma.cpp
 *
 * Created on: May 5, 2022
 * Author: som
 */

#include "Mamma.h"
#include "UncleanedState.h"
#include "CleanedState.h"
#include "MarinatedState.h"
#include "CookedState.h"
#include <iostream>

Mamma::Mamma() {
    t.setCallback(this);
    // Initialize with the starting state
    currentState = std::make_unique<UncleanedState>(this);
}

void Mamma::changeChickenState(std::unique_ptr<ChickenState> nextState) {
    currentState = std::move(nextState);
}

void Mamma::startPreparation() {
    while (timerRunning) {
        if (!isUncleanedStateDone) {
            currentState->wash(1000, 1500);
        } else if (!isCleanedStateDone) {
            currentState->marinate(1000, 1500);
        } else if (!isMarinatedStateDone) {
            currentState->cook(200, 2000);
        } else if (!isCookedStateDone) {
            currentState->serve(200, 1000);
        }
    }
    std::cout << "Mamma - the cook is leaving the kitchen.\n";
}

void Mamma::goToNextStateFromCallback() {
    t.justStop();
    currentState->goToNextState();
}

Enter fullscreen mode Exit fullscreen mode

#ifndef TIMER_H_
#define TIMER_H_

#include <thread>
#include <chrono>
#include <atomic>
#include "Callback.h"

class Timer {
private:
    Callback* cb = nullptr;
    std::atomic<bool> active{true};

public:
    Timer() = default;

    void setCallback(Callback* callback) {
        cb = callback;
    }

    void setTimeout(auto function, int delay) {
        active = true;
        std::thread t([this, function, delay]() {
            if (!active.load()) return;
            std::this_thread::sleep_for(std::chrono::milliseconds(delay));
            if (!active.load()) return;
            function();
        });
        t.join(); // Blocks parent execution sequentially per step
    }

    void stop() {
        if (cb) cb->goToNextStateFromCallback();
    }

    void justStop() {
        active = false;
    }
};

#endif

Enter fullscreen mode Exit fullscreen mode

//============================================================================
// Name        : Main.cpp
// Author      : Som
// Version     :
// Copyright   : som-itsolutions
// Description : Hello World in C++, Ansi-style
//============================================================================

#include "Mamma.h"

int main() {
    auto mamma = std::make_unique<Mamma>();
    mamma->startPreparation();
    return 0;
}

Enter fullscreen mode Exit fullscreen mode

Top comments (0)