Online Bookstore: Low-Level Design (LLD) with Design Patterns and Java Code
In this answer, we will design an Online Bookstore system that handles books, patrons, orders, and inventory management. We will demonstrate how various design patterns can be applied to manage these entities, improve efficiency, and scale the application.
Design Overview
The Online Bookstore system will include the following key components:
Book: Represents a book in the store.
Patron: Represents a customer who can browse, order, and pay for books.
Order: Represents a customer's order, including payment and fulfillment.
Inventory: Manages the stock of books and restocking levels.
SearchEngine: Provides the functionality for searching books by title, author, subject, and other attributes.
PaymentProcessor: Handles the payment and transaction processing for orders.
We'll incorporate design patterns such as:
Factory Pattern for creating different types of objects.
Observer Pattern for notifying patrons about order updates.
Strategy Pattern for different search algorithms.
Singleton Pattern for managing shared instances like the Inventory and PaymentProcessor.
Composite Pattern to manage a collection of books and categories.
Entities and Relationships
1. Book Class:
The Book class represents a single book entity. It will contain attributes like title, author, subject, and stock (number of copies available).
class Book {
private String title;
private String author;
private String subject;
private double price;
private int stock;
public Book(String title, String author, String subject, double price, int stock) {
this.title = title;
this.author = author;
this.subject = subject;
this.price = price;
this.stock = stock;
}
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getSubject() { return subject; }
public double getPrice() { return price; }
public int getStock() { return stock; }
public void decreaseStock() { if (stock > 0) stock--; }
public void increaseStock(int quantity) { stock += quantity; }
}
2. Patron Class:
The Patron class represents a customer in the system, with information such as name, email, and a list of orders they've placed.
class Patron {
private String name;
private String email;
private List<Order> orders;
public Patron(String name, String email) {
this.name = name;
this.email = email;
this.orders = new ArrayList<>();
}
public String getName() { return name; }
public String getEmail() { return email; }
public List<Order> getOrders() { return orders; }
public void addOrder(Order order) {
orders.add(order);
}
}
3. Order Class:
The Order class represents an order placed by a patron. It contains a list of books, the order's status, and payment information.
import java.util.List;
class Order {
private Patron patron;
private List<Book> books;
private double totalAmount;
private String status;
private boolean isPaid;
public Order(Patron patron, List<Book> books) {
this.patron = patron;
this.books = books;
this.totalAmount = books.stream().mapToDouble(Book::getPrice).sum();
this.status = "Pending";
this.isPaid = false;
}
public double getTotalAmount() { return totalAmount; }
public String getStatus() { return status; }
public void setStatus(String status) { this.status = status; }
public boolean isPaid() { return isPaid; }
public void markPaid() { this.isPaid = true; }
public void addBook(Book book) {
books.add(book);
totalAmount += book.getPrice();
}
public List<Book> getBooks() { return books; }
}
4. Inventory Class (Singleton Pattern):
The Inventory class will be a Singleton to manage the stock levels of all books. It will ensure that stock is updated correctly when orders are placed and restocked.
import java.util.HashMap;
import java.util.Map;
class Inventory {
private static Inventory instance;
private Map<String, Book> books;
private Inventory() {
books = new HashMap<>();
}
public static Inventory getInstance() {
if (instance == null) {
instance = new Inventory();
}
return instance;
}
public void addBook(Book book) {
books.put(book.getTitle(), book);
}
public Book getBook(String title) {
return books.get(title);
}
public void updateStock(String title, int quantity) {
Book book = books.get(title);
if (book != null) {
book.increaseStock(quantity);
}
}
public boolean isStockAvailable(String title, int quantity) {
Book book = books.get(title);
return book != null && book.getStock() >= quantity;
}
}
5. SearchEngine Class (Strategy Pattern):
The SearchEngine class provides the ability to search for books by various attributes like title, author, and subject. We will use the Strategy Pattern to allow different search algorithms.
Placing an Order: A patron selects books to buy. The system checks if the books are available in stock. If available, the order is created.
Payment: Once the order is created, the PaymentProcessor processes the payment through the selected method (e.g., Credit Card, PayPal).
Fulfillment: After payment is successful, the order is marked as "Completed," and the stock is updated.
Inventory Management: If an order is placed, the inventory is updated by decreasing the stock of the books purchased. The system also monitors the stock levels and triggers restocking when stock runs low.
Main Class (Integration)
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
// Initialize inventory with some books
Inventory inventory = Inventory.getInstance();
inventory.addBook(new Book("Harry Potter", "J.K. Rowling", "Fantasy", 20.0, 10));
inventory.addBook(new Book("Clean Code", "Robert C. Martin", "Programming", 30.0, 5));
// Create a patron (customer)
Patron patron = new Patron("John Doe", "john.doe@example.com");
// Create an order
Book book1 = inventory.getBook("Harry Potter");
Book book2 = inventory.getBook("Clean Code");
Order order = new Order(patron, Arrays.asList(book1, book2));
// Check stock and place order
if (inventory.isStockAvailable("Harry Potter", 1) && inventory.isStockAvailable("Clean Code", 1)) {
// Process payment
PaymentProcessor paymentProcessor = new PaymentProcessor();
paymentProcessor.setPaymentStrategy(new CreditCardPayment());
if (paymentProcessor.processPayment(order)) {
order.setStatus("Completed");
System.out.println("Order placed successfully!");
}
}
// Search books using strategy pattern
SearchEngine searchEngine = new SearchEngine();
searchEngine.setSearchStrategy(new TitleSearch());
List<Book> searchResults = searchEngine.search(Arrays.asList(book1, book2), "Harry Potter");
searchResults.forEach(book -> System.out.println("Found: " + book.getTitle()));
}
}
Explanation of Design Patterns Used
Factory Pattern: Used for creating different types of books, patrons, and payment strategies dynamically.
Singleton Pattern: Used for Inventory and PaymentProcessor to ensure only one instance of these classes is created, shared across the system.
Strategy Pattern: Applied in the SearchEngine and PaymentProcessor classes to allow switching between different search algorithms (e.g., title search, author search) and payment methods (e.g., credit card, PayPal).
Observer Pattern: (Can be added later for notifying patrons about order updates).
Composite Pattern: Could be applied to manage books and categories, treating books and categories uniformly if we need to expand to a more complex category structure in the future.
Conclusion
This Low-Level Design (LLD) of the Online Bookstore demonstrates how to efficiently manage books, patrons, and orders, leveraging design patterns to create a scalable and maintainable system. It handles book searching, inventory management, order processing, and payment handling, all while allowing for future extensions such as adding new search strategies, payment methods, or inventory management techniques.