Vending Machine System Design
π 1. Requirements Gathering
β Functional Requirements
Product Selection: Users should be able to select a product.
Payment Handling: Accept coins and bills.
Inventory Management: Maintain stock levels of each product.
Change Dispensation: Return change if overpaid.
Refund Option: Users can cancel and get their money back.
Display Information: Show product price, balance, and status messages.
β Non-Functional Requirements
Scalability: Support more product types in the future.
Maintainability: Modular design to easily add new features.
Thread Safety: Ensure multiple users canβt interfere with each other's transactions.
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
State Pattern: Handle different vending machine states (Idle, MoneyInserted, ProductDispensed).
Strategy Pattern: Calculate change and process payment dynamically.
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!");
}
}