Below is a comprehensive Low-Level Design (LLD) for a Shopping Cart System implemented in Java. This design models the main entities (Products, Users, and Carts) and their relationships, details efficient cart operations (add, update, remove items), and outlines the checkout process with payment processing and order confirmation. We also incorporate several design patterns (Singleton, Factory, Strategy, and Observer) directly into the code to ensure modularity, scalability, and maintainability while providing an intuitive user experience.
1. Overview
Entities and Relationships
Product:
Represents an item available for purchase, with details like ID, name, description, price, and available stock.
User:
Represents a shopper with authentication details and a reference to their shopping cart.
Cart & CartItem:
Cart: Maintains a list of CartItem objects and supports operations such as adding, updating, and removing items.
CartItem: Represents an entry in the cart and is associated with a Product and a specified quantity.
Cart Operations
Efficient Updates:
The Cart class provides methods to add, update, and remove items. It uses in-memory data structures (such as a HashMap for quick lookup by product ID) to ensure efficiency.
Checkout Process
Order Creation:
The checkout process converts the cart into an Order object.
Payment Processing:
A PaymentService using the Strategy Pattern handles payment via interchangeable strategies (e.g., Credit Card, PayPal).
Order Confirmation:
Once payment is successful, the order is confirmed and stored as part of the user's order history.
User Experience
Authentication (Singleton):AuthenticationService securely logs in users.
Observer Pattern for Updates:
Observers (e.g., mobile or email notifications) can subscribe to order events (like successful checkout) for a real‑time, responsive user experience.
Intuitive UI Design (Conceptual):
Although the UI isn’t implemented in this code sample, the design envisions a web/mobile front-end that interacts with these services via REST APIs, providing a smooth, intuitive shopping experience.
2. Detailed Java Code
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
// --------------------- ENTITIES ---------------------
// Product represents an item available for purchase.
class Product {
private String productId;
private String name;
private String description;
private double price;
private int stock;
public Product(String productId, String name, String description, double price, int stock) {
this.productId = productId;
this.name = name;
this.description = description;
this.price = price;
this.stock = stock;
}
public String getProductId() { return productId; }
public String getName() { return name; }
public String getDescription() { return description; }
public double getPrice() { return price; }
public synchronized int getStock() { return stock; }
public synchronized boolean reduceStock(int quantity) {
if (stock >= quantity) {
stock -= quantity;
return true;
}
return false;
}
public synchronized void increaseStock(int quantity) {
stock += quantity;
}
}
// CartItem represents an entry in the shopping cart.
class CartItem {
private Product product;
private int quantity;
public CartItem(Product product, int quantity) {
this.product = product;
this.quantity = quantity;
}
public Product getProduct() { return product; }
public int getQuantity() { return quantity; }
public void setQuantity(int quantity) { this.quantity = quantity; }
public double getTotalPrice() {
return product.getPrice() * quantity;
}
}
// Cart maintains a list of CartItems and supports operations to manage items.
class Cart {
// Using a HashMap for efficient lookup by product ID.
private Map<String, CartItem> items;
public Cart() {
items = new HashMap<>();
}
// Add a product to the cart.
public void addItem(Product product, int quantity) {
String productId = product.getProductId();
if (items.containsKey(productId)) {
// Update quantity if already present.
CartItem item = items.get(productId);
item.setQuantity(item.getQuantity() + quantity);
} else {
items.put(productId, new CartItem(product, quantity));
}
}
// Update the quantity of a product in the cart.
public void updateItem(Product product, int quantity) {
String productId = product.getProductId();
if (items.containsKey(productId)) {
if (quantity <= 0) {
items.remove(productId);
} else {
items.get(productId).setQuantity(quantity);
}
}
}
// Remove an item from the cart.
public void removeItem(Product product) {
items.remove(product.getProductId());
}
// Get all items in the cart.
public Collection<CartItem> getItems() {
return items.values();
}
// Calculate the total cart value.
public double getTotalAmount() {
double total = 0.0;
for (CartItem item : items.values()) {
total += item.getTotalPrice();
}
return total;
}
// Clear the cart.
public void clear() {
items.clear();
}
}
// Order represents a confirmed order after checkout.
class Order {
private String orderId;
private String userId;
private List<CartItem> orderItems;
private double totalAmount;
public Order(String orderId, String userId, List<CartItem> orderItems, double totalAmount) {
this.orderId = orderId;
this.userId = userId;
this.orderItems = orderItems;
this.totalAmount = totalAmount;
}
public String getOrderId() { return orderId; }
public String getUserId() { return userId; }
public List<CartItem> getOrderItems() { return orderItems; }
public double getTotalAmount() { return totalAmount; }
}
// User represents a customer with authentication details and a shopping cart.
class User {
private String userId;
private String name;
private String email;
private String password; // In production, should be hashed.
private Cart cart;
private List<Order> orderHistory;
public User(String userId, String name, String email, String password) {
this.userId = userId;
this.name = name;
this.email = email;
this.password = password;
this.cart = new Cart();
this.orderHistory = new ArrayList<>();
}
public String getUserId() { return userId; }
public String getName() { return name; }
public String getEmail() { return email; }
public String getPassword() { return password; }
public Cart getCart() { return cart; }
public List<Order> getOrderHistory() { return orderHistory; }
public void addOrder(Order order) {
orderHistory.add(order);
}
}
// --------------------- AUTHENTICATION SERVICE ---------------------
/*
* Singleton Pattern: AuthenticationService handles user login securely.
*/
class AuthenticationService {
private static AuthenticationService instance;
private Map<String, User> userDB;
private AuthenticationService() {
userDB = new ConcurrentHashMap<>();
// Pre-load dummy users.
userDB.put("john@example.com", new User("U001", "John Doe", "john@example.com", "pass123"));
userDB.put("jane@example.com", new User("U002", "Jane Smith", "jane@example.com", "pass456"));
}
public static synchronized AuthenticationService getInstance() {
if (instance == null) {
instance = new AuthenticationService();
}
return instance;
}
// Authenticate user by email and password.
public User authenticate(String email, String password) {
User user = userDB.get(email);
if (user != null && user.getPassword().equals(password)) {
return user;
}
return null;
}
}
// --------------------- PAYMENT PROCESSING (STRATEGY PATTERN) ---------------------
/*
* Strategy Pattern: PaymentStrategy defines how to process payments.
*/
interface PaymentStrategy {
Payment processPayment(double amount);
}
// Payment entity representing the payment details.
class Payment {
private String paymentId;
private double amount;
private String paymentMethod;
private boolean isSuccessful;
public Payment(String paymentId, double amount, String paymentMethod) {
this.paymentId = paymentId;
this.amount = amount;
this.paymentMethod = paymentMethod;
this.isSuccessful = false;
}
public String getPaymentId() { return paymentId; }
public double getAmount() { return amount; }
public String getPaymentMethod() { return paymentMethod; }
public boolean isSuccessful() { return isSuccessful; }
public void markAsSuccessful() { isSuccessful = true; }
}
// Concrete strategy for Credit Card payments.
class CreditCardPaymentStrategy implements PaymentStrategy {
@Override
public Payment processPayment(double amount) {
Payment payment = new Payment(UUID.randomUUID().toString(), amount, "CreditCard");
payment.markAsSuccessful();
System.out.println("Processed Credit Card payment for $" + amount);
return payment;
}
}
// Concrete strategy for PayPal payments.
class PayPalPaymentStrategy implements PaymentStrategy {
@Override
public Payment processPayment(double amount) {
Payment payment = new Payment(UUID.randomUUID().toString(), amount, "PayPal");
payment.markAsSuccessful();
System.out.println("Processed PayPal payment for $" + amount);
return payment;
}
}
/*
* Singleton Pattern: PaymentService uses a PaymentStrategy to process payments.
*/
class PaymentService {
private static PaymentService instance;
private PaymentStrategy paymentStrategy;
private PaymentService() {
paymentStrategy = new CreditCardPaymentStrategy();
}
public static synchronized PaymentService getInstance() {
if (instance == null) {
instance = new PaymentService();
}
return instance;
}
public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}
public Payment processPayment(double amount) {
return paymentStrategy.processPayment(amount);
}
}
// --------------------- OBSERVER PATTERN FOR ORDER CONFIRMATION ---------------------
/*
* Observer Pattern: OrderObserver defines a method to receive order updates.
*/
interface OrderObserver {
void update(Order order, String message);
}
// Concrete observer that simulates sending email notifications.
class EmailOrderObserver implements OrderObserver {
@Override
public void update(Order order, String message) {
// Simulate sending an email.
System.out.println("Email Notification to user: Order " + order.getOrderId() + " - " + message);
}
}
// --------------------- ORDER MANAGEMENT ---------------------
/*
* Singleton & Factory Pattern: OrderManager handles checkout, converts cart to order,
* and notifies observers upon order confirmation.
*/
class OrderManager {
private static OrderManager instance;
private List<OrderObserver> observers;
private OrderManager() {
observers = new ArrayList<>();
}
public static synchronized OrderManager getInstance() {
if (instance == null) {
instance = new OrderManager();
}
return instance;
}
public void registerObserver(OrderObserver observer) {
observers.add(observer);
}
private void notifyObservers(Order order, String message) {
for (OrderObserver observer : observers) {
observer.update(order, message);
}
}
// Convert a user's cart into an order during checkout.
public Order checkout(User user) {
Cart cart = user.getCart();
if (cart.getItems().isEmpty()) {
System.out.println("Cart is empty. Cannot checkout.");
return null;
}
Order order = new Order(UUID.randomUUID().toString(), user.getUserId(), new ArrayList<>(cart.getItems()), cart.getTotalAmount());
user.addOrder(order);
// Clear the cart after checkout.
cart.clear();
notifyObservers(order, "Order placed successfully.");
return order;
}
}
// --------------------- MAIN APPLICATION ---------------------
public class ShoppingCartApp {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// --------------------- User Authentication ---------------------
AuthenticationService authService = AuthenticationService.getInstance();
System.out.println("Welcome to the Shopping Cart System!");
System.out.println("Please log in with your email:");
String email = scanner.nextLine();
System.out.println("Enter your password:");
String password = scanner.nextLine();
User user = authService.authenticate(email, password);
if (user == null) {
System.out.println("Authentication failed. Exiting...");
scanner.close();
return;
}
System.out.println("Welcome, " + user.getName() + "!");
// --------------------- Product Catalog ---------------------
// For simplicity, let's create some products manually.
Product product1 = new Product("P001", "Laptop", "High performance laptop", 1200.00, 10);
Product product2 = new Product("P002", "Smartphone", "Latest model smartphone", 800.00, 20);
Product product3 = new Product("P003", "Headphones", "Noise-cancelling headphones", 150.00, 15);
// Simulate adding products to the user's cart.
Cart cart = user.getCart();
cart.addItem(product1, 1);
cart.addItem(product2, 2);
cart.addItem(product3, 1);
System.out.println("Items added to cart. Total amount: $" + cart.getTotalAmount());
// --------------------- Cart Operations ---------------------
// Update an item in the cart.
cart.updateItem(product2, 1); // Changing quantity of Smartphone to 1.
System.out.println("Cart updated. New total: $" + cart.getTotalAmount());
// Remove an item.
cart.removeItem(product3);
System.out.println("Item removed. New total: $" + cart.getTotalAmount());
// --------------------- Checkout Process ---------------------
OrderManager orderManager = OrderManager.getInstance();
// Register an observer for order confirmations.
orderManager.registerObserver(new EmailOrderObserver());
Order order = orderManager.checkout(user);
if (order == null) {
System.out.println("Checkout failed.");
scanner.close();
return;
}
System.out.println("Order ID: " + order.getOrderId() + " | Order Total: $" + order.getTotalAmount());
// --------------------- Payment Processing ---------------------
PaymentService paymentService = PaymentService.getInstance();
System.out.println("Processing payment...");
Payment payment = paymentService.processPayment(order.getTotalAmount());
if (payment.isSuccessful()) {
System.out.println("Payment successful! Payment ID: " + payment.getPaymentId());
} else {
System.out.println("Payment failed!");
}
// --------------------- Order Confirmation ---------------------
System.out.println("Checkout and payment complete. Order confirmed.");
scanner.close();
}
}
3. Explanation of Key Points & Design Patterns
Entities and Relationships
Product, User, Cart, CartItem, Order:
Product: Stores product information and inventory details.
User: Represents the customer; each user has a shopping cart and order history.
Cart & CartItem: Manage items the user intends to purchase.
Order: Represents a confirmed purchase, created during checkout from the cart contents.
Cart Operations
Efficient Add/Update/Remove:
The Cart uses a HashMap keyed by product ID for efficient item lookup and update operations. Methods allow for adding, updating (adjusting quantities), and removing items.
Checkout Process
OrderManager (Singleton & Factory Pattern):
Converts the cart into an order during checkout, clears the cart, and notifies observers.
PaymentService & PaymentStrategy (Strategy Pattern):
Process payments via interchangeable strategies (e.g., Credit Card, PayPal).
User Experience
AuthenticationService (Singleton):
Securely logs in users.
Observer Pattern:
Order observers (such as EmailOrderObserver) are notified of order events (e.g., order placement and confirmation), which can be extended to update real‑time UI or send notifications.
Intuitive Operations:
The system’s operations (cart management and checkout) are designed for efficiency and clarity, ensuring an intuitive experience for the shopper.
4. Conclusion
This detailed LLD for a Shopping Cart System demonstrates:
How major entities (Products, Users, Carts, and Orders) interact.
How efficient cart operations are implemented using in‑memory structures.
How the checkout process is handled through order creation, payment processing (using the Strategy Pattern), and order confirmation.
How a seamless user experience is ensured via secure authentication and real‑time notifications (Observer Pattern).
By employing multiple design patterns (Singleton, Factory, Strategy, and Observer), the design is modular, scalable, and maintainable—providing a robust blueprint suitable for technical interviews and production-level implementations.