Low Level Design - Design a Vending Machine
Requirements
- The vending machine is divided into shelves with each shelf contains a specific product type.
- User selects a product by entering the shelf code.
- User selects the payment option.
- On successful payment, the product is dispensed.
- (Optional) Vending Machine sends notification to admin on Out of stock.
Class Diagram
enum ProductType
CHIPS, NACHOS, COOKIES, CRACKERS
enum UserType
CUSTOMER, ADMIN
enum PaymentType
CARD, UPI, CASH
class Product
-------------
- id: String
- name: String
- price: Double
- productType: ProductType
class ProductShelf
------------------
- shelfCode: Integer
- product: Product
- productCount: Integer
------------------------
+ getProduct(): Product
+ addProduct(): void
+ reduceCount(): void
class VendingMachine
--------------------
- id: String
- productShelves: ProductShelf[]
--------------------------------
+ selectProduct(): void
+ makePayment(): void
Design Choices
Incorporating Object-Oriented Design Patterns into the Vending Machine Low-Level Design can significantly enhance the system's modularity, maintainability, and scalability.
1. State Pattern
Managing the state of the vending machine, which can change based on the Order Status like product selection, payment, or product dispensing state.
interface VendingMachineState {
void selectProduct(int shelfCode);
void cancelPayment(String transactionId);
void makePayment(String transactionId, PaymentType paymentType);
void dispenseProduct(String transactionId);
}
class ProductSelectionState implements VendingMachineState {
private VendingMachine vendingMachine;
@Override
public void selectProduct(final int shelfCode) {
// operation
vendingMachine.setCurrentState(new PaymentState(vendingMachine));
}
}
class PaymentState implements VendingMachineState {
private VendingMachine vendingMachine;
@Override
public void makePayment(final String transactionId, final PaymentType paymentType) {
// operation
vendingMachine.setCurrentState(new DispensingState(vendingMachine));
}
@Override
public void cancelPayment(final String transactionId) {
// operation
vendingMachine.setCurrentState(new ProductSelectionState(vendingMachine));
}
}
class DispensingState implements VendingMachineState {
private VendingMachine vendingMachine;
@Override
public void dispenseProduct(final String transactionId) {
// operation
vendingMachine.setCurrentState(new ProductSelectionState(vendingMachine));
}
}
2. Strategy Pattern
To encapsulate algorithms or processes that can vary independently, like different payment processing methods or product selection strategies.
public interface PaymentStrategy {
boolean processPayment(double amount);
}
public class CardPayment implements PaymentStrategy {
@Override
public boolean processPayment(double amount) {
// Card payment logic
return true;
}
}
public class DigitalPayment implements PaymentStrategy {
@Override
public boolean processPayment(double amount) {
// Digital payment logic
return true;
}
}
```java
public class VendingMachine {
public static void main(String[] args) {
PaymentStrategy paymentStragey = new DigitalPayment();
paymentStrategy.processPayment(150);
}
}
3. Adapter Pattern
Integrating with external systems or APIs, like payment gateways or inventory management systems, which may have different interfaces.
// Interface used by the vending machine to interact with all types of payment gateways.
public interface PaymentGateway {
boolean processPayment(double amount);
}
class DigitalPaymentClient {
public boolean makePayment(double amount) {
// Digital payment logic
return true;
}
}
class CardPaymentClient {
public boolean executeTransaction(double amount) {
// Card payment logic
return true;
}
}
// Adapter for Digital Payment
class DigitalPaymentAdapter implements PaymentGateway {
private DigitalPaymentClient digitalPaymentClient;
@Override
public boolean processPayment(double amount) {
return digitalPaymentClient.makePayment(amount);
}
}
// Adapter for Card Payment
class CardPaymentAdapter implements PaymentGateway {
private CardPaymentClient cardPaymentClient;
@Override
public boolean processPayment(double amount) {
return cardPaymentClient.executeTransaction(amount);
}
}
4. (Optional) Observer Pattern
For notifying changes in the machine state or stock levels to various components such as the monitoring system or user interface.
public interface VendingMachineObserver {
void update(String product, int quantity);
}
// Subject
public class VendingMachine {
private Map<String, Integer> stock;
private List<VendingMachineObserver> observers;
public void dispenseProduct(String product) {
int currentStock = stock.getOrDefault(product, 0);
if (currentStock > 0) {
stock.put(product, currentStock - 1);
} else {
notifyObservers();
}
}
@Override
public void registerObserver(VendingMachineObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(VendingMachineObserver observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (VendingMachineObserver observer : observers) {
for (Map.Entry<String, Integer> entry : stock.entrySet()) {
observer.update(entry.getKey(), entry.getValue());
}
}
}
}
public class Admin extends User implements VendingMachineObserver {
@Override
public void update(String product, int quantity) {
if (quantity == 0) {
System.out.println("Monitoring Alert: " + product + " is out of stock!");
}
}
}
API Specifications
Select Product
- API:
POST /vending/product
- Request Body
- shelfCode: Integer
Make Payment
- API:
POST /vending/payment
- Request Body:
- amount: double
- paymentMethod: String