samedi 13 juin 2020

Refactor elevator design using C++ and SOLID principles (Loosely coupled and no inter-dependency)

I have created an simple elevator design for 100 floors building with just one elevator.

I have dependency between classes which I want to eliminate, any thoughts/suggestion to make it more better in-terms of SOLID principles?

I have created following set of classes and detailed their purpose

class Request: This holds all the request made from the 'floor' keypad and inside the keypad. 'm_floor' represent the floor to which Elevator need to travel i.e. If floor keypad pressed from 7th floor then m_floor set as 7.

m_direction represent the DIrection in which lift need to travel after boarding. That means 0th floor can have Direction as 'UP' only and 100th floor can have direction 'DOWN' only

class RequestData: Holds all the previous request made and stored in down or up queue based on the intended direction to go.

...

#pragma once
#include<queue>
#include<memory>
#include<chrono>
#include<mutex>
#include <fstream>
#include <sstream>
#include <iostream>
#include <thread>

using namespace std::chrono;

std::mutex mu;
std::condition_variable cond;

constexpr int MAX_REQ_SIZE = 100;


enum class Direction {
    UP, DOWN
};

enum class State {
    MOVING, STOPPED
};

enum class Door {
    OPEN, CLOSED
};

class Request
{
public:
    milliseconds m_time;
    int m_floor;
    Direction m_direction;

    Request(const Request&) = default;
    Request& operator=(const Request&) = default;


    Request(milliseconds time, int floor, Direction direction)
    {
        m_time = time;
        m_floor = floor;
        m_direction = direction;
    }
};

using UNIQUE_REQ = std::shared_ptr<Request>;

struct UpComparator {
    bool operator()(UNIQUE_REQ r1, UNIQUE_REQ r2)
    {
        // return "true" if r1 floor value is less than r2
        return r1->m_floor < r2->m_floor;
    }
};

struct DownComparator {
    bool operator()(UNIQUE_REQ r1, UNIQUE_REQ r2)
    {
        // return "true" if r1 floor value greater than r2
        return r1->m_floor > r2->m_floor;
    }
};

class RequestData
{
public:

    void AddToDownQueu(UNIQUE_REQ req)
    {
        downQueue.emplace(req);
        if (down_request_time == milliseconds::zero())
        {
            down_request_time = req->m_time;
        }
    }

    void AddToUpQueu(UNIQUE_REQ req)
    {
        upQueue.emplace(req);
        if (up_request_time == milliseconds::zero())
        {
            up_request_time = req->m_time;
        }
    }

    milliseconds up_request_time{ milliseconds::zero() };
    milliseconds down_request_time{ milliseconds::zero() };
    std::priority_queue<UNIQUE_REQ, std::vector<UNIQUE_REQ>, UpComparator> upQueue;
    std::priority_queue<UNIQUE_REQ, std::vector<UNIQUE_REQ>, DownComparator> downQueue; 
};

class ElevatorStatus
{
public: 
    State m_elevator_state{ State::STOPPED };
    Door m_door_state{Door::CLOSED};
    Direction m_current_direction{ Direction::UP };
    std::queue<UNIQUE_REQ> m_current_queue;
    float m_current_location{ 0 };
};


class ElevatorProcessor
{
public:
    ElevatorProcessor() : m_request_data{ std::make_unique<RequestData>() },
        current_elevator_status{ std::make_unique<ElevatorStatus>() }
    {

    }

    void ProcessCurrentRequests()
    {
        while (true)
        {
            std::unique_lock<std::mutex> locker(mu);
            cond.wait(locker, [this]() {return (!current_elevator_status->m_current_queue.empty() ||
                !m_request_data->upQueue.empty() ||
                !m_request_data->downQueue.empty()); });

            if (!current_elevator_status->m_current_queue.empty())
            {
                auto r = current_elevator_status->m_current_queue.front();
                goToFloor(r->m_floor);

                current_elevator_status->m_current_queue.pop();
            }
            else
            {
                preProcessNextQueue();
            }

        }
    }

    std::unique_ptr<RequestData> m_request_data;
    std::unique_ptr<ElevatorStatus> current_elevator_status;

private:
    void goToFloor(int floor) {
        std::cout << "Navigating to the floor: " << floor << std::endl;
        current_elevator_status->m_elevator_state = State::MOVING;
        for (float i = current_elevator_status->m_current_location;
            i <= floor; i = (float)(i + 0.1))
        {
            std::this_thread::sleep_for(0.02s);
        }

        current_elevator_status->m_current_location = floor;
        current_elevator_status->m_door_state = Door::OPEN;
        current_elevator_status->m_elevator_state = State::STOPPED;

        std::this_thread::sleep_for(5s);

        current_elevator_status->m_door_state = Door::CLOSED;
    }

    void preProcessNextQueue()
    {
        if (m_request_data->up_request_time > m_request_data->down_request_time)
        {
            current_elevator_status->m_current_direction = Direction::UP;
            while (!m_request_data->upQueue.empty())
            {
                current_elevator_status->m_current_queue.push(m_request_data->upQueue.top());
                m_request_data->upQueue.pop();
            }
            m_request_data->up_request_time = milliseconds::zero();

        }
        else
        {
            current_elevator_status->m_current_direction = Direction::DOWN;
            while (!m_request_data->downQueue.empty())
            {
                current_elevator_status->m_current_queue.push(m_request_data->downQueue.top());
                m_request_data->downQueue.pop();
            }
            m_request_data->down_request_time = milliseconds::zero();
        }
    }
};

class ElevatorUser
{
public:

    ElevatorUser(std::shared_ptr<ElevatorProcessor> proc) :m_elevator_proc{ proc }
    {

    }

    void ReadInputData(const std::string filepath)
    {
        std::string line{};

        std::ifstream infile(filepath);
        while (std::getline(infile, line))
        {
            std::istringstream iss(line);
            int floor;
            std::string str_direction;
            if (!(iss >> floor >> str_direction)) { break; } // error
            Direction dir = (str_direction == "up") ? Direction::UP : Direction::DOWN;

            if (dir == Direction::UP)
            {
                std::cout << "Placing reuest for goin UP to floor: " << floor << std::endl;
            }
            else
            {
                std::cout << "Placing reuest for goin DOWN to floor: " << floor << std::endl;
            }
            call(floor, dir);
            std::this_thread::sleep_for(1s);
        }

    }
private: 
    void call(int to_floor, Direction direction)
    {
        std::unique_lock<std::mutex> locker(mu);
        cond.wait(locker, [this]() {return m_elevator_proc->current_elevator_status->m_current_queue.size() < MAX_REQ_SIZE; });

        milliseconds current_time = duration_cast<milliseconds>(
            system_clock::now().time_since_epoch());

        UNIQUE_REQ new_req = std::make_shared<Request>(current_time, to_floor, direction);


        if (direction == Direction::UP)
        {
            if (to_floor >= m_elevator_proc->current_elevator_status->m_current_location)
            {
                m_elevator_proc->current_elevator_status->m_current_queue.emplace(new_req);
            }
            else
            {
                m_elevator_proc->m_request_data->AddToUpQueu(new_req);
            }
        }
        else
        {
            if (to_floor <= m_elevator_proc->current_elevator_status->m_current_location)
            {
                m_elevator_proc->current_elevator_status->m_current_queue.emplace(new_req);
            }
            else
            {
                m_elevator_proc->m_request_data->AddToDownQueu(new_req);
            }
        }

        locker.unlock();
        cond.notify_one();

    }

    std::shared_ptr<ElevatorProcessor> m_elevator_proc;
};

int main(void)
{
    std::shared_ptr<ElevatorProcessor> elevator_proc = std::make_shared<ElevatorProcessor>();

    std::unique_ptr<ElevatorUser> elevator_user = std::make_unique<ElevatorUser>(elevator_proc);

    const std::string path = "D:\\hacker_input\\elevator_input.txt";
    std::thread t1(&ElevatorUser::ReadInputData, elevator_user.get(), path);

    std::thread t2(&ElevatorProcessor::ProcessCurrentRequests, elevator_proc.get());

    t1.join();
    t2.join();
    return 0;
}

...

Aucun commentaire:

Enregistrer un commentaire