This content originally appeared on DEV Community and was authored by Anjali Jha
In this article, I will try my hands on designing and implementing a solution for vending machine using Golang
The requirements
- The vending machine should support multiple products with different prices and quantities.
- The machine should accept coins and notes of different denominations.
- The machine should dispense the selected product and return change if necessary.
- The machine should keep track of the available products and their quantities.
- The machine should handle multiple transactions concurrently and ensure data consistency.
- The machine should provide an interface for restocking products and collecting money.
- The machine should handle exceptional scenarios, such as insufficient funds or out-of-stock products.
Analysis
Upon seeing the question, I can figure that the machine works differently based on its state. These states can be-
- No money inserted (Idle)
- Has money but not yet purchased (HasMoney)
- Currently dispensing (Dispensing)
- Return money/change (ReturnChange)
Instead of huge if/else blocks, we can use the State Pattern which lets each state define valid actions (InsertMoney, SelectProduct, Cancel).
Flow Example
- Machine starts in idleState.
- User inserts coin → IdleState.InsertCoin() is called → moves to readyState.
- User selects product → ReadyState.SelectProduct() checks stock + payment.
- If enough payment → transitions to dispenseState, calls DispenseProduct().
- If change needed → transitions to returnChangeState, calls ReturnChange().
- After transaction → back to idleState.
The approach here will be to first create an interface which defines possible actions. We will then implement the interface using object instance.
We will also need few classes here
- Product: which shows the product of vending machine
- Coin and Note : represent the denominations of coins and notes
- VendingMachine : main class that represents the vending machine
Application
We will first create state interface, assuming we have struct in place for Product(name, price, qty), Coin (penny, quarter), Note(ten,twenty)-
type VendingMachineState interface {
SelectProduct(product *Product)
InsertCoin(coin Coin)
InsertNote(note Note)
DispenseProduct()
ReturnChange()
}
Now we will create VendingMachine context struct. This holds products, cash inventory, balance, and current state and provides methods for state transitions and payment handling.
type VendingMachine struct {
inventory map[*Product]int
//state
idleState VendingMachineState
readyState VendingMachineState
dispenseState VendingMachineState
returnChangeState VendingMachineState
currentState VendingMachineState
// transaction/runtime
selectedProduct *Product
totalPayment float64
}
We initialize a new Vending Machine. This creates a vending machine with an empty inventory and concrete state objects (IdleState, ReadyState, etc.). It follows the Singleton pattern to ensure only one instance of the vending machine exists. The snippet also has delegation methods.This means the behavior changes depending on the current state.-
func NewVendingMachine() *VendingMachine {
vm := &VendingMachine{
inventory: NewInventory(),
}
vm.idleState = &IdleState{vm}
vm.readyState = &ReadyState{vm}
vm.dispenseState = &DispenseState{vm}
vm.returnChangeState = &ReturnChangeState{vm}
vm.currentState = vm.idleState
return vm
}
func (vm *VendingMachine) SelectProduct(product *Product) {
vm.currentState.SelectProduct(product)
}
func (vm *VendingMachine) InsertCoin(coin Coin) {
vm.currentState.InsertCoin(coin)
}
func (vm *VendingMachine) InsertNote(note Note) {
vm.currentState.InsertNote(note)
}
func (vm *VendingMachine) DispenseProduct() {
vm.currentState.DispenseProduct()
}
func (vm *VendingMachine) ReturnChange() {
vm.currentState.ReturnChange()
}
We can also create few utility methods to set state, resetPayment.
Now to the main part –
The magic happens in concrete state implementations. Each state (IdleState, ReadyState, etc.) implements the VendingMachineState methods. They decide when to call SetState to move to another state. For example –
// IdleState struct
type IdleState struct {
vendingMachine *VendingMachine
}
func (s *IdleState) SelectProduct(product *Product) {
if s.vendingMachine.inventory.IsAvailable(product) {
s.vendingMachine.selectedProduct = product
s.vendingMachine.SetState(s.vendingMachine.readyState)
fmt.Println("Product selected:", product.name)
} else {
fmt.Println("Product not available:", product.name)
}
}
func (s *IdleState) InsertCoin(coin Coin) { fmt.Println("Please select a product first.") }
func (s *IdleState) InsertNote(note Note) { fmt.Println("Please select a product first.") }
func (s *IdleState) DispenseProduct() { fmt.Println("Please select a product and make payment.") }
func (s *IdleState) ReturnChange() { fmt.Println("No change to return.") }
Similarly other states can be implemented. You can add a demo file to demonstrate the entire functionality by adding products to the inventory, selecting products, inserting coins and notes, dispensing products, and returning change.
Hope this helps! Always open to suggestions for improvement.
This content originally appeared on DEV Community and was authored by Anjali Jha