Vending Machine System Design

Β·

6 min read

πŸ“š 1. Requirements Gathering

βœ… Functional Requirements

  1. Product Selection: Users should be able to select a product.

  2. Payment Handling: Accept coins and bills.

  3. Inventory Management: Maintain stock levels of each product.

  4. Change Dispensation: Return change if overpaid.

  5. Refund Option: Users can cancel and get their money back.

  6. Display Information: Show product price, balance, and status messages.

βœ… Non-Functional Requirements

  1. Scalability: Support more product types in the future.

  2. Maintainability: Modular design to easily add new features.

  3. Thread Safety: Ensure multiple users can’t interfere with each other's transactions.

  4. Robust Error Handling: Gracefully handle errors (e.g., insufficient funds, out-of-stock).


🧠 2. Key Components and Breakdown

2.1. Product

  • Represents items in the vending machine (e.g., chips, soda, candy).

  • Attributes: id, name, price, quantity.

2.2. Inventory Management

  • Tracks available products.

  • Updates stock after a product is dispensed.

2.3. Payment System

  • Accepts coins and bills.

  • Calculates change.

  • Refunds money if the transaction is canceled.

2.4. State Management

  • The vending machine operates in different states:

    • Idle: Waiting for user input.

    • Money Inserted: User has inserted payment.

    • Product Dispensed: Product is released.

    • Refund: Money is refunded.

2.5. Display

  • Show status messages (Insert Coin, Select Product, Transaction Complete, etc.).

2.6. VendingMachine Class

  • Orchestrates all operations: manages state transitions, interacts with inventory, payment, and display.

πŸ“ 3. Design Patterns Used

  1. State Pattern: Handle different vending machine states (Idle, MoneyInserted, ProductDispensed).

  2. Strategy Pattern: Calculate change and process payment dynamically.

  3. Singleton Pattern: Ensure only one instance of the vending machine exists.


πŸ“ 4. Class Diagram

plaintextCopy codeVendingMachine
- Inventory inventory
- State currentState
- double currentBalance

+ selectProduct(id: int)
+ insertMoney(amount: double)
+ dispenseProduct()
+ refund()

Inventory
- Map<Product, Integer> stock

+ addProduct(Product product, int quantity)
+ updateStock(Product product)
+ getProductById(id: int)

Product
- int id
- String name
- double price

Payment
+ acceptMoney(amount: double)
+ refundMoney()

State (interface)
+ insertMoney(amount: double)
+ selectProduct(id: int)
+ dispense()
+ refund()

πŸ”„ 5. Breakdown of Complex Parts

5.1. State Management (State Pattern)

  • IdleState: Waiting for user interaction.

  • HasMoneyState: Money has been inserted, waiting for product selection.

  • DispensingState: Product is being dispensed.

  • RefundState: Money is refunded.

5.2. Payment Handling

  • Maintain currentBalance in the vending machine.

  • Ensure change is calculated and returned accurately.

5.3. Inventory Management

  • Track products and their quantities.

  • Prevent product selection if out of stock.


πŸ’» 6. Implementation

πŸ“¦ 6.1. Product Class

public class Product {
    private final int id;
    private final String name;
    private final double price;

    public Product(int id, String name, double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public double getPrice() {
        return price;
    }
}

πŸ“¦ 6.2. Inventory Class

import java.util.HashMap;
import java.util.Map;

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

    public void addProduct(Product product, int quantity) {
        stock.put(product, stock.getOrDefault(product, 0) + quantity);
    }

    public boolean isAvailable(Product product) {
        return stock.getOrDefault(product, 0) > 0;
    }

    public void reduceStock(Product product) {
        if (isAvailable(product)) {
            stock.put(product, stock.get(product) - 1);
        }
    }

    public Product getProductById(int id) {
        for (Product product : stock.keySet()) {
            if (product.getId() == id) {
                return product;
            }
        }
        return null;
    }
}

πŸ“¦ 6.3. State Interface

public interface State {
    void insertMoney(double amount);
    void selectProduct(int productId);
    void dispense();
    void refund();
}

πŸ“¦ 6.4. Concrete States

IdleState

public class IdleState implements State {
    private final VendingMachine machine;

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

    public void insertMoney(double amount) {
        machine.addMoney(amount);
        machine.setState(new HasMoneyState(machine));
        System.out.println("Money inserted: $" + amount);
    }

    public void selectProduct(int productId) {
        System.out.println("Please insert money first.");
    }

    public void dispense() {
        System.out.println("Please insert money and select a product.");
    }

    public void refund() {
        System.out.println("No money to refund.");
    }
}

HasMoneyState

public class HasMoneyState implements State {
    private final VendingMachine machine;

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

    public void insertMoney(double amount) {
        machine.addMoney(amount);
        System.out.println("Additional money inserted: $" + amount);
    }

    public void selectProduct(int productId) {
        Product product = machine.getInventory().getProductById(productId);
        if (product == null || !machine.getInventory().isAvailable(product)) {
            System.out.println("Product unavailable.");
            return;
        }

        if (machine.getBalance() >= product.getPrice()) {
            machine.getInventory().reduceStock(product);
            machine.setState(new DispensingState(machine, product));
        } else {
            System.out.println("Insufficient funds. Please insert more money.");
        }
    }

    public void dispense() {
        System.out.println("Please select a product first.");
    }

    public void refund() {
        System.out.println("Refunding money: $" + machine.getBalance());
        machine.resetBalance();
        machine.setState(new IdleState(machine));
    }
}

DispensingState

public class DispensingState implements State {
    private final VendingMachine machine;
    private final Product product;

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

    public void insertMoney(double amount) {
        System.out.println("Dispensing in progress. Please wait.");
    }

    public void selectProduct(int productId) {
        System.out.println("Dispensing in progress. Please wait.");
    }

    public void dispense() {
        System.out.println("Dispensing product: " + product.getName());
        machine.deductBalance(product.getPrice());
        machine.setState(new IdleState(machine));
    }

    public void refund() {
        System.out.println("Cannot refund while dispensing.");
    }
}

πŸ“¦ 6.5. VendingMachine Class

public class VendingMachine {
    private State state;
    private final Inventory inventory;
    private double balance;

    public VendingMachine() {
        this.inventory = new Inventory();
        this.state = new IdleState(this);
    }

    public void setState(State state) {
        this.state = state;
    }

    public Inventory getInventory() {
        return inventory;
    }

    public double getBalance() {
        return balance;
    }

    public void addMoney(double amount) {
        balance += amount;
    }

    public void deductBalance(double amount) {
        balance -= amount;
    }

    public void resetBalance() {
        balance = 0;
    }
}

πŸ“¦ VendingMachineDemo

public class VendingMachineDemo {
    public static void main(String[] args) {
        // Initialize the vending machine
        VendingMachine vendingMachine = new VendingMachine();

        // Add products to the inventory
        Product soda = new Product(1, "Soda", 1.50);
        Product chips = new Product(2, "Chips", 2.00);
        Product candy = new Product(3, "Candy", 1.00);

        vendingMachine.getInventory().addProduct(soda, 2); // 2 sodas in stock
        vendingMachine.getInventory().addProduct(chips, 1); // 1 chips in stock
        vendingMachine.getInventory().addProduct(candy, 0); // 0 candies in stock (out of stock)

        System.out.println("βœ… Initial setup completed!\n");

        // Test Case 1: Valid Purchase
        System.out.println("πŸ›’ Test Case 1: Valid Purchase");
        vendingMachine.setState(new IdleState(vendingMachine));
        vendingMachine.state.insertMoney(2.00); // Insert $2
        vendingMachine.state.selectProduct(1); // Select Soda
        vendingMachine.state.dispense();
        System.out.println("Balance after purchase: $" + vendingMachine.getBalance() + "\n");

        // Test Case 2: Insufficient Balance
        System.out.println("❌ Test Case 2: Insufficient Balance");
        vendingMachine.setState(new IdleState(vendingMachine));
        vendingMachine.state.insertMoney(1.00); // Insert $1
        vendingMachine.state.selectProduct(2); // Select Chips ($2 required)
        vendingMachine.state.refund();
        System.out.println("Balance after refund: $" + vendingMachine.getBalance() + "\n");

        // Test Case 3: Out of Stock
        System.out.println("❌ Test Case 3: Out of Stock");
        vendingMachine.setState(new IdleState(vendingMachine));
        vendingMachine.state.insertMoney(1.00); // Insert $1
        vendingMachine.state.selectProduct(3); // Select Candy (Out of stock)
        vendingMachine.state.refund();
        System.out.println("Balance after refund: $" + vendingMachine.getBalance() + "\n");

        // Test Case 4: Invalid Product Selection
        System.out.println("❌ Test Case 4: Invalid Product Selection");
        vendingMachine.setState(new IdleState(vendingMachine));
        vendingMachine.state.insertMoney(1.00); // Insert $1
        vendingMachine.state.selectProduct(99); // Invalid product ID
        vendingMachine.state.refund();
        System.out.println("Balance after refund: $" + vendingMachine.getBalance() + "\n");

        // Test Case 5: Refund Before Purchase
        System.out.println("πŸ’΅ Test Case 5: Refund Before Purchase");
        vendingMachine.setState(new IdleState(vendingMachine));
        vendingMachine.state.insertMoney(1.50); // Insert $1.50
        vendingMachine.state.refund();
        System.out.println("Balance after refund: $" + vendingMachine.getBalance() + "\n");

        System.out.println("βœ… All test cases completed!");
    }
}
Β