DEV Community

DevCorner2
DevCorner2

Posted on

πŸ₯€ Designing a Vending Machine in Java β€” LLD with OOP and Design Patterns

A hands-on guide to building a modular, extensible vending machine using object-oriented principles and design patterns in Java.


🧭 Table of Contents

  1. Introduction
  2. System Requirements
  3. Design Goals
  4. Class Design Overview
  5. Design Patterns Used
  6. UML Diagram
  7. Code Implementation
  8. Sample Output
  9. Conclusion & Next Steps

πŸ”° Introduction

The Vending Machine system is a frequently asked LLD question for junior to mid-level software engineers in interviews. It checks your ability to:

  • Design systems using encapsulation, state transition, and behavioral logic
  • Apply object-oriented principles for clarity and reusability
  • Leverage design patterns to enhance scalability

In this blog, we’ll model and build a console-based vending machine that dispenses items, accepts coins, returns change, and handles errors gracefully.


βœ… System Requirements

Functional Requirements:

  • Display available products with prices
  • Accept money (coins only)
  • Dispense selected item if sufficient money is inserted
  • Return change if applicable
  • Cancel order and refund inserted coins

Non-Functional Requirements:

  • Easy to extend for new item types or coin denominations
  • Clean state management
  • Configurable item inventory

🎯 Design Goals

  • Adhere to SOLID principles
  • Isolate business logic using State pattern
  • Maintain clean separation of concerns
  • Make the system testable and extensible

πŸ—οΈ Class Design Overview

Class/Interface Responsibility
VendingMachine Core machine logic and state management
Item Represents a product with price and name
Coin (enum) Supported denominations
Inventory Tracks available items and coins
VendingMachineState Interface for machine state transitions
IdleState, HasMoneyState, DispensingState Concrete state implementations
StateContext Maintains current state

🧰 Design Patterns Used

Pattern Role
State To manage machine states (Idle β†’ HasMoney β†’ Dispense)
Factory (optional) To generate items or states dynamically
Strategy (optional) For change calculation algorithm

πŸ“ UML Diagram (Simplified)

+------------------------+
|    VendingMachine      |
+------------------------+
| - inventory: Inventory |
| - currentState: VendingMachineState |
+------------------------+
| +insertCoin(Coin)      |
| +selectItem(String)    |
| +cancel()              |
| +dispense()            |
+------------------------+
             |
             β–Ό
+-----------------------------+
|   <<interface>> VendingMachineState |
+-----------------------------+
| +insertCoin(Coin)           |
| +selectItem(String)         |
| +cancel()                   |
| +dispense()                 |
+-----------------------------+
   β–²         β–²         β–²
   |         |         |
+----------+ +----------------+ +-------------------+
| IdleState| | HasMoneyState  | | DispensingState   |
+----------+ +----------------+ +-------------------+

+------------------------+
|        Coin (enum)     |
| PENNY, NICKEL, DIME... |
+------------------------+

+------------------------+
|        Item            |
| - name: String         |
| - price: int           |
+------------------------+

+------------------------+
|      Inventory         |
| - items: Map<Item, Integer> |
+------------------------+
Enter fullscreen mode Exit fullscreen mode

🧱 Code Implementation

βœ… Coin.java

public enum Coin {
    PENNY(1), NICKEL(5), DIME(10), QUARTER(25);

    private final int value;

    Coin(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Item.java

public class Item {
    private final String name;
    private final int price; // In cents

    public Item(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() { return name; }
    public int getPrice() { return price; }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Inventory.java

public class Inventory {
    private final Map<Item, Integer> items = new HashMap<>();

    public void addItem(Item item, int quantity) {
        items.put(item, items.getOrDefault(item, 0) + quantity);
    }

    public boolean hasItem(Item item) {
        return items.getOrDefault(item, 0) > 0;
    }

    public void dispenseItem(Item item) {
        items.put(item, items.get(item) - 1);
    }

    public Set<Item> getAvailableItems() {
        return items.keySet();
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… VendingMachineState.java

public interface VendingMachineState {
    void insertCoin(Coin coin);
    void selectItem(String itemName);
    void cancel();
    void dispense();
}
Enter fullscreen mode Exit fullscreen mode

βœ… IdleState.java

public class IdleState implements VendingMachineState {
    private final VendingMachine machine;

    public IdleState(VendingMachine machine) {
        this.machine = machine;
    }

    public void insertCoin(Coin coin) {
        machine.addBalance(coin.getValue());
        machine.setState(new HasMoneyState(machine));
    }

    public void selectItem(String itemName) {
        System.out.println("Insert coin first.");
    }

    public void cancel() {
        System.out.println("Nothing to cancel.");
    }

    public void dispense() {
        System.out.println("Insert coin first.");
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… HasMoneyState.java

public class HasMoneyState implements VendingMachineState {
    private final VendingMachine machine;

    public HasMoneyState(VendingMachine machine) {
        this.machine = machine;
    }

    public void insertCoin(Coin coin) {
        machine.addBalance(coin.getValue());
    }

    public void selectItem(String itemName) {
        Item item = machine.getItemByName(itemName);
        if (item == null || !machine.getInventory().hasItem(item)) {
            System.out.println("Item not available.");
            return;
        }

        if (machine.getBalance() >= item.getPrice()) {
            machine.setSelectedItem(item);
            machine.setState(new DispensingState(machine));
        } else {
            System.out.println("Insufficient balance.");
        }
    }

    public void cancel() {
        machine.returnChange();
        machine.setState(new IdleState(machine));
    }

    public void dispense() {
        System.out.println("Select item first.");
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… DispensingState.java

public class DispensingState implements VendingMachineState {
    private final VendingMachine machine;

    public DispensingState(VendingMachine machine) {
        this.machine = machine;
    }

    public void insertCoin(Coin coin) {
        System.out.println("Dispensing in progress...");
    }

    public void selectItem(String itemName) {
        System.out.println("Already dispensing...");
    }

    public void cancel() {
        System.out.println("Dispensing in progress...");
    }

    public void dispense() {
        Item item = machine.getSelectedItem();
        machine.getInventory().dispenseItem(item);
        machine.deductBalance(item.getPrice());
        machine.returnChange();
        System.out.println("Dispensed: " + item.getName());
        machine.setState(new IdleState(machine));
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… VendingMachine.java

public class VendingMachine {
    private VendingMachineState currentState;
    private final Inventory inventory = new Inventory();
    private int balance = 0;
    private Item selectedItem;

    public VendingMachine() {
        this.currentState = new IdleState(this);
    }

    public void insertCoin(Coin coin) { currentState.insertCoin(coin); }
    public void selectItem(String name) { currentState.selectItem(name); }
    public void cancel() { currentState.cancel(); }
    public void dispense() { currentState.dispense(); }

    // State and logic management
    public void setState(VendingMachineState state) { this.currentState = state; }
    public void addBalance(int value) { this.balance += value; }
    public void deductBalance(int value) { this.balance -= value; }
    public void returnChange() {
        System.out.println("Returning change: " + balance + " cents");
        balance = 0;
    }

    public void setSelectedItem(Item item) { this.selectedItem = item; }
    public Item getSelectedItem() { return selectedItem; }
    public Inventory getInventory() { return inventory; }
    public int getBalance() { return balance; }

    public Item getItemByName(String name) {
        return inventory.getAvailableItems().stream()
            .filter(i -> i.getName().equalsIgnoreCase(name))
            .findFirst().orElse(null);
    }
}
Enter fullscreen mode Exit fullscreen mode

βœ… Main.java

public class Main {
    public static void main(String[] args) {
        VendingMachine machine = new VendingMachine();
        Item coke = new Item("Coke", 25);
        Item pepsi = new Item("Pepsi", 35);
        machine.getInventory().addItem(coke, 5);
        machine.getInventory().addItem(pepsi, 3);

        machine.insertCoin(Coin.QUARTER);
        machine.selectItem("Coke");
        machine.dispense();

        machine.insertCoin(Coin.DIME);
        machine.insertCoin(Coin.QUARTER);
        machine.selectItem("Pepsi");
        machine.cancel();
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ–¨οΈ Sample Output

Returning change: 0 cents
Dispensed: Coke
Returning change: 10 cents
Enter fullscreen mode Exit fullscreen mode

βœ… Conclusion & Next Steps

You’ve now built a stateful, object-oriented vending machine using:

  • State pattern for behavior transitions
  • Enums for coins
  • Encapsulation for inventory logic

Top comments (0)