Designing a Vending Machine software using the State design pattern
A few years ago, I was interviewing with a Tech major & was asked to design a Vending Machine in one of the rounds. Back then, I had less experience with practical software design & didn’t find time to cram the ‘Gang of Four Design Patterns’ book.
However, I knew the theory well & thanks to the book ‘Cracking the Coding Interview’, I had understood the approach to tackle a software design interview question. In this article, I’ll walk you through the journey of how I started with a naive solution, improved the design, & finished off with a clean, modular and readable code.
Vending Machine design was an open-ended and vague problem. At first thought, many different images flashed my mind and the problem looked intimidating. I decided to drill down the requirements by asking the Interviewer as many questions as I could.
After all the clarification, the expectation was to build software the could store items (track the inventory), accept cash, & dispense items. Additionally, the interviewer wanted the design and code to be extensible, reusable & modular.
I listed the following requirements for the above problem statement
- Vending Machine must keep track of the inventory
- A person should be able to insert cash into the machine & choose an item
- The Machine should confirm the inserted cash with the price of the selected item
- The machine must display an error in case of insufficient cash or unavailable item
- Finally, if all the above steps succeed then the user gets the selected item
In any Object-oriented design interview, it always helps to identify the actors and the actions performed by them. In the above case, we have two actors Vending Machine and the User interacting. The user issues a command to the Machine and the Machine takes the necessary action.
If you think of buying an item like a transaction, the machine only processes one transaction at a time. For eg: If the machine is in the process of dispensing an item, then the user can’t insert cash and try to buy another item. After the machine dispenses the item, the user can buy a new item.
In simple words, a user can buy a new item by either aborting or completing the existing transaction. To solve this we can define different states of the Vending Machine. Depending on the request, either the machine can change it’s state or stay in the same state.
We can define the following states for the Vending Machine:-
- Ready — Machine ready to accept cash
- CashCollected — Machine has collected cash & user can now select the product or cancel the transaction
- DispenseChange — Give back the change to the user
- DispenseItem — Dispense the item upon successful validation of entered cash & the price of the selected item in inventory
- TransactionCancelled — If the user cancels the transaction, return the cash given by the user
Let’s take a look at the code that I wrote initially during the interview. I came up with the below naive code.
The class ‘VendingMachine’ exposed different methods to the user for interaction. Further, it encapsulated all the business logic to process the user commands. The code looked clumsy to me & I was sure the Interviewer would ask me a difficult question.
On taking a look, the Interviewer’s first question was ‘Does your design confirm to the S.O.L.I.D principles? ’. I quickly glanced at my code & assed the effort needed to introduce a new state. It would require the introduction of a new class with an additional switch-case block.
State-related logic is hard-coded in the VendingMachine class which means the Single-Responsibility principle is violated. Besides, a new feature requires me to modify the same class thus going against the Open-Closed principle.
I pondered over it for a while & eventually the thought of using State Design Pattern crossed my mind.
State Design Pattern
The core principle behind the State design pattern is to abstract out the state-related behaviour in a separate class. A context class stores a reference to the state class. The states can be accessed through a common interface. For every state-transition, the reference to the specific state class is modified.
Following is a UML representation of the State design pattern:-
For the Vending Machine design, we can declare a state interface which exposes the APIs — collectCash, dispenseChange, dispenseItem, cancelTransaction
All the states that we identified will implement the state interface. The Vending Machine becomes a context and stores a reference to the state.
Vending Machine class will delegate all the actions that it receives to the specific state classes. The individual states will process the command and perform a state transition by resetting the state in the context. Let’s have look at my modified code.
With the above code, a new state can be easily defined and plugged into the existing implementation with minimal change. Further, individual states are decoupled from each other. Finally, I had a vending machine codebase which was reusable, extensible, readable & clean.
- The design pattern moves all state-related logic to a separate class thus reducing the coupling with the main context class & is following the Single Responsibility Principle
- State-related behaviour is declared in an interface. New states can be easily introduced without the need to modify & add conditional blocks of code. Code becomes open for extension & closed for modification
- Individual states must be aware of the next states and those states need to be hardcoded
- The pattern becomes an overkill if the design only has one or two states or the state behaviour rarely changes
Note:- You might find the State design pattern similar to the Strategy design pattern which was discussed in my last post here. The only difference is that in Strategy, the concrete strategy classes are not aware of each other whereas, in State pattern, the current state should be aware of the next state.