Low Level Design For A Vending Machine

ยท

6 min read

Understanding the Vending Machine Functionality

  • A vending machine has a grid of products lined up for display, each product has a machine-assigned code(item shelf code) and price visible to the user

  • There is a slot for Inserting Coin/Cash

  • There is a Submit Cash/Coin Button (The only active button initially)

  • There is a keypad for entering item shelf code by the user for product selection

  • There is a Submit Product Code Button (Activates once you enter money equivalent to the price of the cheapest available product)

  • There is a Cash Change Dispensing tray, which spits out the change if any (In case you added extra money but chose an item with a lower price)

  • There Product Dispensing tray via which you get the product you want

USER FLOW :

Problem Analysis

  • We can see that formulating this as a state-based system makes sense

  • There are well-defined states and a list of Triggers (operations) that lead to state change

  • We will hence use a State Machine for our user flow execution

States

Triggers

State Machine

  • we can implement a state machine as a map of the current state to any of the next states via corresponding triggers(see data structure below)

we can now execute this state machine now in the following manner

  • in the above code we are taking action and just changing the state, needless to say, we also need to perform corresponding actions for the triggers

Code Flow

  • Main Initialises a vending machine, this has an initial state, an Inventory, has a CoinList of Coin/Cash, for dispensing change, and all corresponding getter/setters

  • Inventory has all Vending Machine Items organised a a 2D grid fashion, and has operations relating to incrementing/decrementing counts of these Items starting from the manual initialisations from the vending machine operator

UML

CODE


#include <iostream>
#include<queue>
#include<utility>
#include<algorithm>
#include<climits>
#include<cmath>
#include<unordered_map>
#include<unordered_set>
#include<vector>
#include<stack>
#include<sstream>
#include<cstring>
#include<string>
#include <deque>
#include <map>
#include<queue>
#include<bitset>
#include<set>
#include<queue>
#include<cstring>
#include<string>
#include<algorithm>
#include <fstream>
#include <tuple>
using namespace std;
typedef long long  ll;
ll MOD =1e9+7;

enum class State {
  MachineIdle,
  MachineAcceptingMoney,
  MachineSelectingProduct,
  MachineDispensingProduct
};

inline ostream& operator<<(ostream& os, const State& s){// simply for printing
  switch (s){
    case State::MachineIdle:
          os << "Vending Machine is Idle";
    break;
    case State::MachineAcceptingMoney:
          os << "Machine is Aceepting Money";
    break;
      case State::MachineSelectingProduct:
          os << "Machine in Product Selection State";
    break;
      case State::MachineDispensingProduct:
          os << "Machine in Product Dispensing State";
    break;
  }
  return os;
}

enum class Trigger{// events that cause state transition
  InsertCashButtonPressed,
  CoinCashInserted,
  SelectProductButtonPressed,
  CancelButtonPressed,
    ProductDispensed
};


inline ostream& operator<<(ostream& os, const Trigger& t) {
  switch (t) {
    case Trigger::InsertCashButtonPressed:
      os << "Press Insert Cash Button";
      break;
    case Trigger::CoinCashInserted:
      os << "Coin or Cash is inserted";
      break;
    case Trigger::SelectProductButtonPressed:
      os << "Select Product Button Pressed";
      break;
    case Trigger::CancelButtonPressed:
      os << "Cancel Button Pressed";
      break;
    case Trigger::ProductDispensed:
        os << "The Product is successfully dispensed";
        break;
    default:
      os << "Unknown Trigger";
      break;
  }
  return os;
}

int main(){

  map<State, vector<pair<Trigger, State>>> stateMachine;

    State currentState{ State::MachineIdle };
    State exitState{ State::MachineDispensingProduct };

    // Defining state transitions
     stateMachine[State::MachineIdle] = {
       {Trigger::InsertCashButtonPressed, State::MachineAcceptingMoney}
     };

     stateMachine[State::MachineAcceptingMoney] = {
       {Trigger::CoinCashInserted, State::MachineAcceptingMoney},// partial amount entry
       {Trigger::SelectProductButtonPressed, State::MachineSelectingProduct},//user thinks he has entered full amt
       {Trigger::CancelButtonPressed, State::MachineIdle}//user is refunded if any money entered
     };

     stateMachine[State::MachineSelectingProduct] = {
       {Trigger::SelectProductButtonPressed, State::MachineDispensingProduct},// correct code and sufficient balance
       {Trigger::CancelButtonPressed, State::MachineIdle}//user is refunded if any money entered
     };


    stateMachine[State::MachineDispensingProduct] = {{Trigger::ProductDispensed,State::MachineIdle}};

  while (true){
      cout<<currentState<<endl;
  select_trigger:
    cout << "Select a trigger:" << "\n";
    int i = 0;
    for (auto item : stateMachine[currentState])
      cout << i++ << ". " << item.first << "\n";// item.second is the new state

    int input; cin >> input;
    if (input < 0 || (input+1) > stateMachine[currentState].size()){
      cout << "Incorrect option. Please try again." << "\n";
      goto select_trigger;
    }

    currentState = stateMachine[currentState][input].second;
    if (currentState == exitState) break;
  }

    cout<<currentState<<endl;
    cout << "We are done using the Vending Machine" << "\n";

  return 0;
}

More on State Design Pattern

  • A vending machine is a direct implementation of the state design pattern, the state design pattern is also implemented via inheritance in some implementations but that violates ISP principles for example here is a LIGHT BULB implemented via the state design pattern:

  •   // Traditional and Bad Approach for state design pattern
      // Modelling a LightSwithc
       class LightSwitch;
    
       struct State{
         virtual void on(LightSwitch *ls){
           cout << "Light is already on\n";
         }
         virtual void off(LightSwitch *ls){
           cout << "Light is already off\n";
         }
       };
    
       struct OnState : State{
         OnState(){
           cout << "Light turned on\n";
         }
         void off(LightSwitch* ls) override;
       };
    
       struct OffState : State{
         OffState(){
           cout << "Light turned off\n";
         }
         void on(LightSwitch* ls) override;
       };
    
       class LightSwitch{
         State *state;
       public:
         LightSwitch(){
           state = new OffState();
         }
         void set_state(State* state){
           this->state = state;
         }
         void on() { state->on(this); }
         void off() { state->off(this); }
       };
    
       void OnState::off(LightSwitch* ls){
         cout << "Switching light off...\n";
         ls->set_state(new OffState());
         delete this;
       }
    
       void OffState::on(LightSwitch* ls){
         cout << "Switching light on...\n";
         ls->set_state(new OnState());
         delete this;
       }
    
       int main(){
         LightSwitch ls;
         ls.on();
         ls.off();
         ls.off();
         getchar();
           return 0;
       }
    
  • Newer Implementations include use of a hand-coded state machine as we saw in the vending machine example and also below we can see a PhoneCall Modelled using state design pattern

  • 
      #include <iostream>
      #include<queue>
      #include<utility>
      #include<algorithm>
      #include<climits>
      #include<cmath>
      #include<unordered_map>
      #include<unordered_set>
      #include<vector>
      #include<stack>
      #include<sstream>
      #include<cstring>
      #include<string>
      #include <deque>
      #include <map>
      #include<queue>
      #include<bitset>
      #include<set>
      #include<queue>
      #include<cstring>
      #include<string>
      #include<algorithm>
      #include <fstream>
      #include <tuple>
      using namespace std;
      typedef long long  ll;
      ll MOD =1e9+7;
    
      enum class State {
        OffHook,
        Connecting,
        Connected,
        OnHold,
        OnHook
      };
    
      inline ostream& operator<<(ostream& os, const State& s){// simply for printinf enum
        switch (s){
        case State::OffHook:
          os << "off the hook";
          break;
        case State::Connecting:
          os << "connecting";
          break;
        case State::Connected:
          os << "connected";
          break;
        case State::OnHold:
          os << "on hold";
          break;
        case State::OnHook:
          os << "on the hook";
          break;
        }
        return os;
      }
    
      enum class Trigger// events that cause state transition
      {
        CallDialed,
        HungUp,
        CallConnected,
        PlacedOnHold,
        TakenOffHold,
        LeftMessage,
        StopUsingPhone
      };
    
      inline ostream& operator<<(ostream& os, const Trigger& t) {
        switch (t){
        case Trigger::CallDialed:
          os << "call dialed";
          break;
        case Trigger::HungUp:
          os << "hung up";
          break;
        case Trigger::CallConnected:
          os << "call connected";
          break;
        case Trigger::PlacedOnHold:
          os << "placed on hold";
          break;
        case Trigger::TakenOffHold:
          os << "taken off hold";
          break;
        case Trigger::LeftMessage:
          os << "left message";
          break;
        case Trigger::StopUsingPhone:
          os << "putting phone on hook";
          break;
        default: break;
        }
        return os;
      }
    
      int main(){
        map<State, vector<pair<Trigger, State>>> rules;
      // state machine is a set of rules
        rules[State::OffHook] = {
          {Trigger::CallDialed, State::Connecting},
          {Trigger::StopUsingPhone, State::OnHook}
        };// one of the rules of a state machine
    
        rules[State::Connecting] = {
          {Trigger::HungUp, State::OffHook},
          {Trigger::CallConnected, State::Connected}
        };
    
        rules[State::Connected] = {
          {Trigger::LeftMessage, State::OffHook},
          {Trigger::HungUp, State::OffHook},
          {Trigger::PlacedOnHold, State::OnHold}
        };
    
        rules[State::OnHold] = {
          {Trigger::TakenOffHold, State::Connected},
          {Trigger::HungUp, State::OffHook}
        };
    
          State currentState{ State::OffHook };
    
          State exitState{ State::OnHook };// When state machine stops executing
    
        while (true){
          cout << "The phone is currently " << currentState << endl;
    
        select_trigger:
    
          cout << "Select a trigger:" << "\n";
    
          int i = 0;
          for (auto item : rules[currentState]){
            cout << i++ << ". " << item.first << "\n";// item.second is the new state
          }
    
          int input;
          cin >> input;
          if (input < 0 || (input+1) > rules[currentState].size())
          {
            cout << "Incorrect option. Please try again." << "\n";
            goto select_trigger;
          }
    
          currentState = rules[currentState][input].second;
          if (currentState == exitState) break;
        }
    
        cout << "We are done using the phone" << "\n";
    
        return 0;
      }
    
ย