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; }