This was an interview question to be coded in C++:
Write code for a vending machine: Start with a simple one where it just vends one type of item. So two state variables: money and inventory, would do.
My answer:
I would use a state machine which has about 3-4 states. Use an enum variable to indicate the state and use a switch case statement, where each case has the operations to be done corresponding to each state and stay in a loop to move from one state to another.
The next question:
But using a switch case statement does not "scale well" for more states being added and modifying existing operations in a state. How are you going to deal with that problem?
I couldn't answer this question at that time. But later thought, I can probably:
- have different functions for different states (each function corresponding to a state)
- have an
std::map
from (string, function) where string indicates state to call the corresponding state function. - The main function has a string variable (starting in initial state), and calls the function corresponding to that variable in a loop. Each function does the operations needed and returns the new state to the main function.
My questions are:
- What is the problem with switch-case statements with respect to scalability in the context of large scale software systems?
- If so is my solution (which currently I feel is a bit more modular than having long linear code) going to resolve the problem?
The interview question is expecting answers from C++ idioms and design patterns for large scale software systems.
I've written plenty of state machines using these methods. But when I wrote Cisco's Transceiver Library for the Nexus 7000 (a $117,000 switch) I used a method I invented in the 80's. That was to use a macro which makes the state machine look more like multi-tasking blocking code. The macros are written for C but I have used them with small modifications for C++ when I worked for DELL. You can read more about it here: https://www.codeproject.com/Articles/37037/Macros-to-simulate-multi-tasking-blocking-code-at
I once wrote a state machine in C++, where I needed the same transition for a lot of state pairs (source → target pairs). I want to illustrate an example:
What I came up with was a set of (transition criteria + next state + "action" function to be called). To keep things general, both the transition criteria and the next state were written as functors (lambda functions):
This solution is nice if you have a lot of transitions which apply for a lot of different states as in the example above. However, for each "step", this method requires to linearly scan the list of all different transitions.
For the examples above, there would be two such transitions:
I don't know whether that would have gotten you through the interview, but I'd personally refrain from coding any state machine by hand, especially if it's in a professional setting. State machines are a well researched problem, and there exist well tested open source tools which often produce superior code to what you will produce yourself by hand, and they also help you with diagnosing problems with your state machine by eg. being able to generate state diagrams automatically.
My goto tools for this kind of problem are:
Consider using tables instead of
switch
statements. One column could be the transition criteria and another column is the destination state.This scales nicely because you don't have to change the table processing function; just add another row to the table.
In my code at work, we use a column of function pointers rather than the "Next state ID". The table is a separate file with accessor functions defined. There is one or more include statements to resolve each function pointer.
Edit 1: Example of separate table files.
table.h
table.cpp:
state_machine.cpp
I was thinking in a more OO approach, using the
State Pattern
:The Machine:
The States:
I'm not used to program in C++, but this code aparently compiles against GCC 4.8.2 and valgrind shows no leaks, so I guess it's fine. I'm not computing money, but I don't need this to show you the idea.
To test it:
Output is:
Now, if you want to add a
Broken
state, all you need is anotherAbstractState
child. Maybe you'll need to add abroken
property onMachine
also.To add more products, you must have a map of products and its respective in-stock quantity and so on...