โฑ๏ธ 30 min read
Designing a Library Management System
Difficulty: Beginner Patterns: Strategy, Observer, Factory Asked at: Flipkart, TCS, Infosys, Wipro, Goldman Sachs
Functional Requirements
- Add/search books - add books to catalog, search by title, author, or ISBN
- Issue/return books - members can borrow and return books with due dates
- Fine calculation - compute overdue fines using configurable strategies (per day, flat, tiered)
- Reservation queue - when a book is unavailable, members can reserve and get notified when returned
- Member management - register members, track borrowing history, set borrowing limits
- Overdue tracking - identify overdue books and notify members
Non-Functional Requirements
- Thread-safety - concurrent issue/return operations must not corrupt book state
- Extensibility - new fine strategies, member tiers, or book categories with minimal changes
- Fast search - book lookup by ISBN in O(1), search by title/author efficiently
Core Entities
| Entity | Description |
|---|---|
Book |
ISBN, title, author, category, total copies |
BookCopy |
Individual physical copy of a book with availability status |
Member |
ID, name, membership type, borrowed books list |
MemberType |
Enum: STANDARD, PREMIUM (different borrowing limits) |
BookLoan |
Links member to book copy with issue date, due date, return date |
Reservation |
Member waiting for a specific book |
FineStrategy |
Interface for computing overdue fines |
Library |
Central system managing all operations |
LibraryObserver |
Notified on issue, return, overdue events |
Class Diagram
classDiagram
class MemberType {
<<enumeration>>
STANDARD
PREMIUM
}
class BookStatus {
<<enumeration>>
AVAILABLE
ISSUED
RESERVED
LOST
}
class Book {
-String isbn
-String title
-String author
-String category
-List~BookCopy~ copies
+getAvailableCopy() BookCopy
+getTotalCopies() int
+getAvailableCount() int
}
class BookCopy {
-String copyId
-Book book
-BookStatus status
+isAvailable() boolean
+markIssued()
+markReturned()
}
class Member {
-String id
-String name
-MemberType type
-List~BookLoan~ activeLoans
-int maxBooks
+canBorrow() boolean
+getBorrowedCount() int
}
class BookLoan {
-String id
-Member member
-BookCopy bookCopy
-LocalDate issueDate
-LocalDate dueDate
-LocalDate returnDate
-double fineAmount
+isOverdue() boolean
+getDaysOverdue() int
}
class Reservation {
-Member member
-Book book
-LocalDateTime reservedAt
}
class FineStrategy {
<<interface>>
+calculateFine(int daysOverdue) double
+getDescription() String
}
class PerDayFine {
-double ratePerDay
+calculateFine(int days) double
}
class TieredFine {
+calculateFine(int days) double
}
class FlatFine {
-double amount
+calculateFine(int days) double
}
class LibraryObserver {
<<interface>>
+onBookIssued(BookLoan loan)
+onBookReturned(BookLoan loan)
+onBookOverdue(BookLoan loan)
+onReservationAvailable(Reservation res)
}
class Library {
-Map~String, Book~ catalog
-Map~String, Member~ members
-Map~String, BookLoan~ activeLoans
-Map~String, Queue~Reservation~~ reservations
-FineStrategy fineStrategy
-List~LibraryObserver~ observers
+addBook(Book)
+registerMember(Member)
+issueBook(String memberId, String isbn) BookLoan
+returnBook(String loanId) double
+searchByTitle(String title) List
+searchByAuthor(String author) List
+reserveBook(String memberId, String isbn)
+getOverdueLoans() List
}
Library --> Book
Library --> Member
Library --> BookLoan
Library --> FineStrategy
Library --> LibraryObserver
Book --> BookCopy
BookCopy --> BookStatus
BookLoan --> Member
BookLoan --> BookCopy
Member --> MemberType
FineStrategy <|.. PerDayFine
FineStrategy <|.. TieredFine
FineStrategy <|.. FlatFine
Design Patterns
| Pattern | Where | Why |
|---|---|---|
| Strategy | FineStrategy with PerDay/Tiered/Flat implementations |
Swap fine calculation logic without touching Library code |
| Observer | LibraryObserver notified on issue/return/overdue |
Decouple notifications, analytics from core library logic |
| Factory | Member creation with type-based defaults | STANDARD gets 3 books limit, PREMIUM gets 10 |
Data Structures
| Component | Structure | Why |
|---|---|---|
| Book catalog | HashMap<String, Book> keyed by ISBN |
O(1) lookup by ISBN |
| Title search | Linear scan on catalog values | Acceptable for interview; real system uses full-text index |
| Members | HashMap<String, Member> |
O(1) member lookup |
| Active loans | HashMap<String, BookLoan> |
O(1) loan lookup for returns |
| Reservations | Map<String, Queue<Reservation>> per ISBN |
FIFO reservation; first to reserve gets book first |
| Memberโs loans | ArrayList<BookLoan> per member |
Track active borrows and history |
How It All Fits Together
Hereโs the complete flow for issuing and returning a book:
Issue flow:
- Member requests book โ
issueBook(memberId, isbn) - Library checks: member exists, member hasnโt exceeded borrowing limit
- Finds book by ISBN, gets an available copy
- If no copy available: offer reservation
- Creates BookLoan with issue date and due date (14 days for STANDARD, 30 for PREMIUM)
- Marks copy as ISSUED, adds loan to memberโs active loans
- Notifies observers
Return flow:
- Member returns book โ
returnBook(loanId) - Library finds the loan, calculates overdue days
- FineStrategy computes fine if overdue
- Marks copy as AVAILABLE, sets return date on loan
- Checks reservation queue: if someone waiting, auto-issue to first in queue
- Notifies observers of return (and reservation fulfillment if applicable)
Complete Code
All Classes in Single File
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.time.*;
import java.time.format.*;
import java.util.stream.*;
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum MemberType {
STANDARD(3, 14), // 3 books max, 14 day loan period
PREMIUM(10, 30); // 10 books max, 30 day loan period
private final int maxBooks;
private final int loanDays;
MemberType(int maxBooks, int loanDays) {
this.maxBooks = maxBooks;
this.loanDays = loanDays;
}
public int getMaxBooks() { return maxBooks; }
public int getLoanDays() { return loanDays; }
}
enum BookStatus { AVAILABLE, ISSUED, RESERVED, LOST }
// โโโ Book Copy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookCopy {
private final String copyId;
private final String isbn; // reference to parent book
private BookStatus status;
public BookCopy(String copyId, String isbn) {
this.copyId = copyId;
this.isbn = isbn;
this.status = BookStatus.AVAILABLE;
}
public boolean isAvailable() { return status == BookStatus.AVAILABLE; }
public void markIssued() { status = BookStatus.ISSUED; }
public void markReturned() { status = BookStatus.AVAILABLE; }
public void markLost() { status = BookStatus.LOST; }
public String getCopyId() { return copyId; }
public String getIsbn() { return isbn; }
public BookStatus getStatus() { return status; }
}
// โโโ Book โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Book {
private final String isbn;
private final String title;
private final String author;
private final String category;
private final List<BookCopy> copies;
public Book(String isbn, String title, String author, String category, int numCopies) {
this.isbn = isbn;
this.title = title;
this.author = author;
this.category = category;
this.copies = new ArrayList<>();
for (int i = 1; i <= numCopies; i++) {
copies.add(new BookCopy(isbn + "-C" + i, isbn));
}
}
public BookCopy getAvailableCopy() {
return copies.stream().filter(BookCopy::isAvailable).findFirst().orElse(null);
}
public int getAvailableCount() {
return (int) copies.stream().filter(BookCopy::isAvailable).count();
}
public int getTotalCopies() { return copies.size(); }
public String getIsbn() { return isbn; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public String getCategory() { return category; }
@Override
public String toString() {
return "Book[" + title + " by " + author + " | ISBN=" + isbn +
" | " + getAvailableCount() + "/" + copies.size() + " available]";
}
}
// โโโ Member โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Member {
private final String id;
private final String name;
private final MemberType type;
private final List<String> activeLoanIds;
public Member(String id, String name, MemberType type) {
this.id = id;
this.name = name;
this.type = type;
this.activeLoanIds = new ArrayList<>();
}
public boolean canBorrow() {
return activeLoanIds.size() < type.getMaxBooks();
}
public void addLoan(String loanId) { activeLoanIds.add(loanId); }
public void removeLoan(String loanId) { activeLoanIds.remove(loanId); }
public int getBorrowedCount() { return activeLoanIds.size(); }
public String getId() { return id; }
public String getName() { return name; }
public MemberType getType() { return type; }
public int getMaxBooks() { return type.getMaxBooks(); }
@Override
public String toString() {
return "Member[" + name + " | " + type + " | borrowed=" +
activeLoanIds.size() + "/" + type.getMaxBooks() + "]";
}
}
// โโโ Book Loan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookLoan {
private final String id;
private final Member member;
private final BookCopy bookCopy;
private final String bookTitle;
private final LocalDate issueDate;
private final LocalDate dueDate;
private LocalDate returnDate;
private double fineAmount;
public BookLoan(Member member, BookCopy bookCopy, String bookTitle) {
this.id = "LOAN-" + UUID.randomUUID().toString().substring(0, 6);
this.member = member;
this.bookCopy = bookCopy;
this.bookTitle = bookTitle;
this.issueDate = LocalDate.now();
this.dueDate = issueDate.plusDays(member.getType().getLoanDays());
this.fineAmount = 0;
}
// Constructor with custom dates for testing
public BookLoan(Member member, BookCopy bookCopy, String bookTitle,
LocalDate issueDate, LocalDate dueDate) {
this.id = "LOAN-" + UUID.randomUUID().toString().substring(0, 6);
this.member = member;
this.bookCopy = bookCopy;
this.bookTitle = bookTitle;
this.issueDate = issueDate;
this.dueDate = dueDate;
this.fineAmount = 0;
}
public boolean isOverdue() {
LocalDate checkDate = returnDate != null ? returnDate : LocalDate.now();
return checkDate.isAfter(dueDate);
}
public int getDaysOverdue() {
if (!isOverdue()) return 0;
LocalDate checkDate = returnDate != null ? returnDate : LocalDate.now();
return (int) dueDate.until(checkDate).getDays();
}
public void markReturned(double fine) {
this.returnDate = LocalDate.now();
this.fineAmount = fine;
}
public void markReturnedOn(LocalDate date, double fine) {
this.returnDate = date;
this.fineAmount = fine;
}
public String getId() { return id; }
public Member getMember() { return member; }
public BookCopy getBookCopy() { return bookCopy; }
public LocalDate getIssueDate() { return issueDate; }
public LocalDate getDueDate() { return dueDate; }
public LocalDate getReturnDate() { return returnDate; }
public double getFineAmount() { return fineAmount; }
public String getBookTitle() { return bookTitle; }
@Override
public String toString() {
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd-MMM");
String status = returnDate != null ? "RETURNED" : (isOverdue() ? "OVERDUE" : "ACTIVE");
return "Loan[" + id + " | " + bookTitle + " | " + member.getName() +
" | due=" + dueDate.format(fmt) + " | " + status +
(fineAmount > 0 ? " | fine=โน" + String.format("%.2f", fineAmount) : "") + "]";
}
}
// โโโ Fine Strategy Interface โโโโโโโโโโโโโโโโโโโโโโโโโโโโ
interface FineStrategy {
double calculateFine(int daysOverdue);
String getDescription();
}
// โโโ Per Day Fine โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class PerDayFine implements FineStrategy {
private final double ratePerDay;
public PerDayFine(double ratePerDay) {
this.ratePerDay = ratePerDay;
}
@Override
public double calculateFine(int daysOverdue) {
return daysOverdue * ratePerDay;
}
@Override
public String getDescription() {
return "โน" + ratePerDay + " per day";
}
}
// โโโ Tiered Fine โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class TieredFine implements FineStrategy {
@Override
public double calculateFine(int daysOverdue) {
if (daysOverdue <= 0) return 0;
if (daysOverdue <= 7) return daysOverdue * 2.0; // โน2/day first week
if (daysOverdue <= 14) return 14 + (daysOverdue - 7) * 5.0; // โน5/day second week
return 14 + 35 + (daysOverdue - 14) * 10.0; // โน10/day after
}
@Override
public String getDescription() {
return "Tiered: โน2/day (week 1), โน5/day (week 2), โน10/day (after)";
}
}
// โโโ Flat Fine โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class FlatFine implements FineStrategy {
private final double amount;
public FlatFine(double amount) {
this.amount = amount;
}
@Override
public double calculateFine(int daysOverdue) {
return daysOverdue > 0 ? amount : 0;
}
@Override
public String getDescription() {
return "Flat โน" + amount + " for any overdue";
}
}
// โโโ Reservation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Reservation {
private final Member member;
private final String isbn;
private final LocalDateTime reservedAt;
public Reservation(Member member, String isbn) {
this.member = member;
this.isbn = isbn;
this.reservedAt = LocalDateTime.now();
}
public Member getMember() { return member; }
public String getIsbn() { return isbn; }
public LocalDateTime getReservedAt() { return reservedAt; }
}
// โโโ Library Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
interface LibraryObserver {
void onBookIssued(BookLoan loan);
void onBookReturned(BookLoan loan);
void onBookOverdue(BookLoan loan);
void onReservationAvailable(Member member, String bookTitle);
}
class LoggingLibraryObserver implements LibraryObserver {
@Override
public void onBookIssued(BookLoan loan) {
System.out.println(" [ISSUED] " + loan.getBookTitle() + " to " + loan.getMember().getName() +
" (due: " + loan.getDueDate() + ")");
}
@Override
public void onBookReturned(BookLoan loan) {
System.out.println(" [RETURNED] " + loan.getBookTitle() + " by " + loan.getMember().getName() +
(loan.getFineAmount() > 0 ? " | Fine: โน" + String.format("%.2f", loan.getFineAmount()) : ""));
}
@Override
public void onBookOverdue(BookLoan loan) {
System.out.println(" [OVERDUE] " + loan.getBookTitle() + " - " + loan.getMember().getName() +
" (" + loan.getDaysOverdue() + " days late)");
}
@Override
public void onReservationAvailable(Member member, String bookTitle) {
System.out.println(" [RESERVED] " + bookTitle + " is now available for " + member.getName());
}
}
// โโโ Library โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Library {
private final ConcurrentHashMap<String, Book> catalog; // isbn -> book
private final ConcurrentHashMap<String, Member> members; // memberId -> member
private final ConcurrentHashMap<String, BookLoan> activeLoans; // loanId -> loan
private final ConcurrentHashMap<String, Queue<Reservation>> reservations; // isbn -> queue
private FineStrategy fineStrategy;
private final List<LibraryObserver> observers;
private final ReentrantLock lock;
public Library(FineStrategy fineStrategy) {
this.catalog = new ConcurrentHashMap<>();
this.members = new ConcurrentHashMap<>();
this.activeLoans = new ConcurrentHashMap<>();
this.reservations = new ConcurrentHashMap<>();
this.fineStrategy = fineStrategy;
this.observers = new CopyOnWriteArrayList<>();
this.lock = new ReentrantLock();
}
public void addObserver(LibraryObserver observer) { observers.add(observer); }
public void setFineStrategy(FineStrategy strategy) { this.fineStrategy = strategy; }
public void addBook(Book book) { catalog.put(book.getIsbn(), book); }
public void registerMember(Member member) { members.put(member.getId(), member); }
public BookLoan issueBook(String memberId, String isbn) {
lock.lock();
try {
Member member = members.get(memberId);
if (member == null) throw new RuntimeException("Member not found: " + memberId);
if (!member.canBorrow()) {
throw new RuntimeException(member.getName() + " has reached borrowing limit (" +
member.getMaxBooks() + " books)");
}
Book book = catalog.get(isbn);
if (book == null) throw new RuntimeException("Book not found: " + isbn);
BookCopy copy = book.getAvailableCopy();
if (copy == null) {
throw new RuntimeException("No copies available for: " + book.getTitle() +
". Use reserveBook() to join waitlist.");
}
copy.markIssued();
BookLoan loan = new BookLoan(member, copy, book.getTitle());
activeLoans.put(loan.getId(), loan);
member.addLoan(loan.getId());
observers.forEach(o -> o.onBookIssued(loan));
return loan;
} finally {
lock.unlock();
}
}
public double returnBook(String loanId) {
lock.lock();
try {
BookLoan loan = activeLoans.get(loanId);
if (loan == null) throw new RuntimeException("Loan not found: " + loanId);
int daysOverdue = loan.getDaysOverdue();
double fine = fineStrategy.calculateFine(daysOverdue);
loan.markReturned(fine);
loan.getBookCopy().markReturned();
loan.getMember().removeLoan(loanId);
activeLoans.remove(loanId);
observers.forEach(o -> o.onBookReturned(loan));
// Check reservations
String isbn = loan.getBookCopy().getIsbn();
Queue<Reservation> queue = reservations.get(isbn);
if (queue != null && !queue.isEmpty()) {
Reservation res = queue.poll();
Book book = catalog.get(isbn);
observers.forEach(o -> o.onReservationAvailable(res.getMember(), book.getTitle()));
}
return fine;
} finally {
lock.unlock();
}
}
// Return with specific date (for testing overdue scenarios)
public double returnBookOn(String loanId, LocalDate returnDate) {
lock.lock();
try {
BookLoan loan = activeLoans.get(loanId);
if (loan == null) throw new RuntimeException("Loan not found: " + loanId);
int daysOverdue = 0;
if (returnDate.isAfter(loan.getDueDate())) {
daysOverdue = (int) loan.getDueDate().until(returnDate).getDays();
}
double fine = fineStrategy.calculateFine(daysOverdue);
loan.markReturnedOn(returnDate, fine);
loan.getBookCopy().markReturned();
loan.getMember().removeLoan(loanId);
activeLoans.remove(loanId);
observers.forEach(o -> o.onBookReturned(loan));
String isbn = loan.getBookCopy().getIsbn();
Queue<Reservation> queue = reservations.get(isbn);
if (queue != null && !queue.isEmpty()) {
Reservation res = queue.poll();
Book book = catalog.get(isbn);
observers.forEach(o -> o.onReservationAvailable(res.getMember(), book.getTitle()));
}
return fine;
} finally {
lock.unlock();
}
}
public void reserveBook(String memberId, String isbn) {
lock.lock();
try {
Member member = members.get(memberId);
if (member == null) throw new RuntimeException("Member not found");
Book book = catalog.get(isbn);
if (book == null) throw new RuntimeException("Book not found");
if (book.getAvailableCount() > 0) {
throw new RuntimeException("Book is available - issue it directly");
}
reservations.computeIfAbsent(isbn, k -> new LinkedList<>())
.offer(new Reservation(member, isbn));
System.out.println(" " + member.getName() + " reserved '" + book.getTitle() + "'");
} finally {
lock.unlock();
}
}
public List<Book> searchByTitle(String titleQuery) {
String query = titleQuery.toLowerCase();
return catalog.values().stream()
.filter(b -> b.getTitle().toLowerCase().contains(query))
.collect(Collectors.toList());
}
public List<Book> searchByAuthor(String authorQuery) {
String query = authorQuery.toLowerCase();
return catalog.values().stream()
.filter(b -> b.getAuthor().toLowerCase().contains(query))
.collect(Collectors.toList());
}
public Book searchByIsbn(String isbn) {
return catalog.get(isbn);
}
public List<BookLoan> getOverdueLoans() {
return activeLoans.values().stream()
.filter(BookLoan::isOverdue)
.collect(Collectors.toList());
}
public void displayCatalog() {
System.out.println("\n--- Library Catalog ---");
catalog.values().forEach(b -> System.out.println(" " + b));
}
public void displayMembers() {
System.out.println("\n--- Members ---");
members.values().forEach(m -> System.out.println(" " + m));
}
}
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
public class LibraryManagementDemo {
public static void main(String[] args) {
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" LIBRARY MANAGEMENT - LLD DEMO ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n");
// Setup library with tiered fine strategy
Library library = new Library(new TieredFine());
library.addObserver(new LoggingLibraryObserver());
// โโโ Add Books โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("--- Adding Books ---");
library.addBook(new Book("978-0-13-468599-1", "Clean Code", "Robert C. Martin", "Programming", 3));
library.addBook(new Book("978-0-201-63361-0", "Design Patterns", "Gang of Four", "Programming", 2));
library.addBook(new Book("978-0-596-51774-8", "JavaScript: The Good Parts", "Douglas Crockford", "Programming", 1));
library.addBook(new Book("978-0-06-112008-4", "To Kill a Mockingbird", "Harper Lee", "Fiction", 4));
library.addBook(new Book("978-0-14-028329-7", "1984", "George Orwell", "Fiction", 2));
library.displayCatalog();
// โโโ Register Members โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Registering Members ---");
library.registerMember(new Member("M1", "Rahul", MemberType.STANDARD));
library.registerMember(new Member("M2", "Priya", MemberType.PREMIUM));
library.registerMember(new Member("M3", "Arjun", MemberType.STANDARD));
library.displayMembers();
// โโโ Issue Books โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Issuing Books ---");
BookLoan loan1 = library.issueBook("M1", "978-0-13-468599-1"); // Clean Code
BookLoan loan2 = library.issueBook("M2", "978-0-13-468599-1"); // Clean Code (2nd copy)
BookLoan loan3 = library.issueBook("M1", "978-0-06-112008-4"); // Mockingbird
BookLoan loan4 = library.issueBook("M3", "978-0-596-51774-8"); // JS Good Parts (only 1 copy)
// โโโ Search Books โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Search Results ---");
System.out.println("Search 'clean':");
library.searchByTitle("clean").forEach(b -> System.out.println(" " + b));
System.out.println("Search by author 'orwell':");
library.searchByAuthor("orwell").forEach(b -> System.out.println(" " + b));
// โโโ Reservation (book unavailable) โโโโโโโโโโโโโโโโโ
System.out.println("\n--- Reservation ---");
// JS Good Parts has only 1 copy and it's issued to Arjun
try {
library.issueBook("M2", "978-0-596-51774-8");
} catch (RuntimeException e) {
System.out.println(" " + e.getMessage());
}
library.reserveBook("M2", "978-0-596-51774-8");
// โโโ Return Books (on time) โโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Returning Books (on time) ---");
double fine1 = library.returnBook(loan1.getId());
System.out.println(" Fine for Clean Code: โน" + fine1);
// โโโ Return with Overdue (simulate) โโโโโโโโโโโโโโโโโ
System.out.println("\n--- Returning Overdue Book (10 days late) ---");
LocalDate overdueReturn = loan3.getDueDate().plusDays(10);
double fine3 = library.returnBookOn(loan3.getId(), overdueReturn);
System.out.println(" Fine for Mockingbird (10 days overdue): โน" + String.format("%.2f", fine3));
// โโโ Return JS book - triggers reservation notification
System.out.println("\n--- Returning JS Book (triggers reservation) ---");
library.returnBook(loan4.getId());
// โโโ Borrowing Limit โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Borrowing Limit Test ---");
// Rahul is STANDARD (max 3), currently has 0 active
library.issueBook("M1", "978-0-201-63361-0");
library.issueBook("M1", "978-0-14-028329-7");
library.issueBook("M1", "978-0-06-112008-4");
try {
library.issueBook("M1", "978-0-13-468599-1");
} catch (RuntimeException e) {
System.out.println(" Expected: " + e.getMessage());
}
// โโโ Switch Fine Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโ
System.out.println("\n--- Switch to Per-Day Fine (โน5/day) ---");
library.setFineStrategy(new PerDayFine(5.0));
System.out.println(" Fine for 10 days: โน" + new PerDayFine(5.0).calculateFine(10));
System.out.println("\n--- Fine Strategy Comparison ---");
System.out.println(" Per-day (โน5): 10 days = โน" + new PerDayFine(5).calculateFine(10));
System.out.println(" Tiered: 10 days = โน" + new TieredFine().calculateFine(10));
System.out.println(" Flat (โน50): 10 days = โน" + new FlatFine(50).calculateFine(10));
library.displayCatalog();
library.displayMembers();
System.out.println("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
System.out.println(" DEMO COMPLETE ");
System.out.println("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ");
}
}
import threading
import uuid
from datetime import date, datetime, timedelta
from collections import deque
from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional
# โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class MemberType(Enum):
STANDARD = ("STANDARD", 3, 14) # max 3 books, 14 day loan
PREMIUM = ("PREMIUM", 10, 30) # max 10 books, 30 day loan
def __init__(self, label, max_books, loan_days):
self._label = label
self._max_books = max_books
self._loan_days = loan_days
@property
def max_books(self): return self._max_books
@property
def loan_days(self): return self._loan_days
class BookStatus(Enum):
AVAILABLE = "AVAILABLE"
ISSUED = "ISSUED"
RESERVED = "RESERVED"
LOST = "LOST"
# โโโ Book Copy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookCopy:
def __init__(self, copy_id: str, isbn: str):
self.copy_id = copy_id
self.isbn = isbn
self.status = BookStatus.AVAILABLE
@property
def is_available(self) -> bool:
return self.status == BookStatus.AVAILABLE
def mark_issued(self): self.status = BookStatus.ISSUED
def mark_returned(self): self.status = BookStatus.AVAILABLE
# โโโ Book โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Book:
def __init__(self, isbn: str, title: str, author: str, category: str, num_copies: int):
self.isbn = isbn
self.title = title
self.author = author
self.category = category
self.copies = [BookCopy(f"{isbn}-C{i+1}", isbn) for i in range(num_copies)]
def get_available_copy(self) -> Optional[BookCopy]:
for copy in self.copies:
if copy.is_available:
return copy
return None
@property
def available_count(self) -> int:
return sum(1 for c in self.copies if c.is_available)
def __str__(self) -> str:
return (f"Book[{self.title} by {self.author} | ISBN={self.isbn} | "
f"{self.available_count}/{len(self.copies)} available]")
# โโโ Member โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Member:
def __init__(self, member_id: str, name: str, member_type: MemberType):
self.id = member_id
self.name = name
self.type = member_type
self._active_loan_ids: list[str] = []
def can_borrow(self) -> bool:
return len(self._active_loan_ids) < self.type.max_books
def add_loan(self, loan_id: str): self._active_loan_ids.append(loan_id)
def remove_loan(self, loan_id: str): self._active_loan_ids.remove(loan_id)
@property
def borrowed_count(self) -> int: return len(self._active_loan_ids)
def __str__(self) -> str:
return (f"Member[{self.name} | {self.type.name} | "
f"borrowed={self.borrowed_count}/{self.type.max_books}]")
# โโโ Book Loan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class BookLoan:
def __init__(self, member: Member, book_copy: BookCopy, book_title: str,
issue_date: Optional[date] = None):
self.id = f"LOAN-{uuid.uuid4().hex[:6]}"
self.member = member
self.book_copy = book_copy
self.book_title = book_title
self.issue_date = issue_date or date.today()
self.due_date = self.issue_date + timedelta(days=member.type.loan_days)
self.return_date: Optional[date] = None
self.fine_amount = 0.0
@property
def is_overdue(self) -> bool:
check = self.return_date or date.today()
return check > self.due_date
@property
def days_overdue(self) -> int:
if not self.is_overdue:
return 0
check = self.return_date or date.today()
return (check - self.due_date).days
def mark_returned(self, fine: float, return_date: Optional[date] = None):
self.return_date = return_date or date.today()
self.fine_amount = fine
def __str__(self) -> str:
status = "RETURNED" if self.return_date else ("OVERDUE" if self.is_overdue else "ACTIVE")
fine_str = f" | fine=Rs{self.fine_amount:.2f}" if self.fine_amount > 0 else ""
return (f"Loan[{self.id} | {self.book_title} | {self.member.name} | "
f"due={self.due_date} | {status}{fine_str}]")
# โโโ Fine Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class FineStrategy(ABC):
@abstractmethod
def calculate_fine(self, days_overdue: int) -> float: pass
@abstractmethod
def description(self) -> str: pass
class PerDayFine(FineStrategy):
def __init__(self, rate_per_day: float):
self._rate = rate_per_day
def calculate_fine(self, days_overdue: int) -> float:
return days_overdue * self._rate
def description(self) -> str:
return f"Rs{self._rate} per day"
class TieredFine(FineStrategy):
def calculate_fine(self, days_overdue: int) -> float:
if days_overdue <= 0: return 0
if days_overdue <= 7: return days_overdue * 2.0
if days_overdue <= 14: return 14 + (days_overdue - 7) * 5.0
return 14 + 35 + (days_overdue - 14) * 10.0
def description(self) -> str:
return "Tiered: Rs2/day (week1), Rs5/day (week2), Rs10/day (after)"
class FlatFine(FineStrategy):
def __init__(self, amount: float):
self._amount = amount
def calculate_fine(self, days_overdue: int) -> float:
return self._amount if days_overdue > 0 else 0
def description(self) -> str:
return f"Flat Rs{self._amount}"
# โโโ Reservation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Reservation:
def __init__(self, member: Member, isbn: str):
self.member = member
self.isbn = isbn
self.reserved_at = datetime.now()
# โโโ Library Observer โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class LibraryObserver(ABC):
@abstractmethod
def on_book_issued(self, loan: BookLoan): pass
@abstractmethod
def on_book_returned(self, loan: BookLoan): pass
@abstractmethod
def on_book_overdue(self, loan: BookLoan): pass
@abstractmethod
def on_reservation_available(self, member: Member, book_title: str): pass
class LoggingObserver(LibraryObserver):
def on_book_issued(self, loan):
print(f" [ISSUED] {loan.book_title} to {loan.member.name} (due: {loan.due_date})")
def on_book_returned(self, loan):
fine = f" | Fine: Rs{loan.fine_amount:.2f}" if loan.fine_amount > 0 else ""
print(f" [RETURNED] {loan.book_title} by {loan.member.name}{fine}")
def on_book_overdue(self, loan):
print(f" [OVERDUE] {loan.book_title} - {loan.member.name} ({loan.days_overdue} days)")
def on_reservation_available(self, member, book_title):
print(f" [RESERVED] {book_title} now available for {member.name}")
# โโโ Library โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Library:
def __init__(self, fine_strategy: FineStrategy):
self._catalog: dict[str, Book] = {}
self._members: dict[str, Member] = {}
self._active_loans: dict[str, BookLoan] = {}
self._reservations: dict[str, deque[Reservation]] = {}
self._fine_strategy = fine_strategy
self._observers: list[LibraryObserver] = []
self._lock = threading.Lock()
def add_observer(self, obs: LibraryObserver): self._observers.append(obs)
def set_fine_strategy(self, s: FineStrategy): self._fine_strategy = s
def add_book(self, book: Book): self._catalog[book.isbn] = book
def register_member(self, member: Member): self._members[member.id] = member
def issue_book(self, member_id: str, isbn: str) -> BookLoan:
with self._lock:
member = self._members.get(member_id)
if not member: raise RuntimeError(f"Member not found: {member_id}")
if not member.can_borrow():
raise RuntimeError(f"{member.name} reached limit ({member.type.max_books})")
book = self._catalog.get(isbn)
if not book: raise RuntimeError(f"Book not found: {isbn}")
copy = book.get_available_copy()
if not copy:
raise RuntimeError(f"No copies available for: {book.title}")
copy.mark_issued()
loan = BookLoan(member, copy, book.title)
self._active_loans[loan.id] = loan
member.add_loan(loan.id)
for obs in self._observers: obs.on_book_issued(loan)
return loan
def return_book(self, loan_id: str, return_date: Optional[date] = None) -> float:
with self._lock:
loan = self._active_loans.get(loan_id)
if not loan: raise RuntimeError(f"Loan not found: {loan_id}")
ret_date = return_date or date.today()
days_over = max(0, (ret_date - loan.due_date).days)
fine = self._fine_strategy.calculate_fine(days_over)
loan.mark_returned(fine, ret_date)
loan.book_copy.mark_returned()
loan.member.remove_loan(loan_id)
del self._active_loans[loan_id]
for obs in self._observers: obs.on_book_returned(loan)
isbn = loan.book_copy.isbn
queue = self._reservations.get(isbn)
if queue:
res = queue.popleft()
book = self._catalog[isbn]
for obs in self._observers:
obs.on_reservation_available(res.member, book.title)
if not queue:
del self._reservations[isbn]
return fine
def reserve_book(self, member_id: str, isbn: str):
with self._lock:
member = self._members.get(member_id)
book = self._catalog.get(isbn)
if not member or not book: raise RuntimeError("Not found")
if book.available_count > 0:
raise RuntimeError("Book is available - issue directly")
self._reservations.setdefault(isbn, deque()).append(Reservation(member, isbn))
print(f" {member.name} reserved '{book.title}'")
def search_by_title(self, query: str) -> list[Book]:
q = query.lower()
return [b for b in self._catalog.values() if q in b.title.lower()]
def search_by_author(self, query: str) -> list[Book]:
q = query.lower()
return [b for b in self._catalog.values() if q in b.author.lower()]
def display_catalog(self):
print("\n--- Library Catalog ---")
for b in self._catalog.values(): print(f" {b}")
def display_members(self):
print("\n--- Members ---")
for m in self._members.values(): print(f" {m}")
# โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
def main():
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" LIBRARY MANAGEMENT - LLD DEMO ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ\n")
library = Library(TieredFine())
library.add_observer(LoggingObserver())
print("--- Adding Books ---")
library.add_book(Book("978-0-13-468599-1", "Clean Code", "Robert C. Martin", "Programming", 3))
library.add_book(Book("978-0-201-63361-0", "Design Patterns", "Gang of Four", "Programming", 2))
library.add_book(Book("978-0-596-51774-8", "JavaScript Good Parts", "Douglas Crockford", "Programming", 1))
library.add_book(Book("978-0-06-112008-4", "To Kill a Mockingbird", "Harper Lee", "Fiction", 4))
library.add_book(Book("978-0-14-028329-7", "1984", "George Orwell", "Fiction", 2))
library.display_catalog()
print("\n--- Registering Members ---")
library.register_member(Member("M1", "Rahul", MemberType.STANDARD))
library.register_member(Member("M2", "Priya", MemberType.PREMIUM))
library.register_member(Member("M3", "Arjun", MemberType.STANDARD))
library.display_members()
print("\n--- Issuing Books ---")
loan1 = library.issue_book("M1", "978-0-13-468599-1")
loan2 = library.issue_book("M2", "978-0-13-468599-1")
loan3 = library.issue_book("M1", "978-0-06-112008-4")
loan4 = library.issue_book("M3", "978-0-596-51774-8")
print("\n--- Search ---")
print("Search 'clean':")
for b in library.search_by_title("clean"): print(f" {b}")
print("Search by author 'orwell':")
for b in library.search_by_author("orwell"): print(f" {b}")
print("\n--- Reservation ---")
try:
library.issue_book("M2", "978-0-596-51774-8")
except RuntimeError as e:
print(f" {e}")
library.reserve_book("M2", "978-0-596-51774-8")
print("\n--- Returning Books (on time) ---")
fine1 = library.return_book(loan1.id)
print(f" Fine: Rs{fine1}")
print("\n--- Returning Overdue Book (10 days late) ---")
overdue_date = loan3.due_date + timedelta(days=10)
fine3 = library.return_book(loan3.id, overdue_date)
print(f" Fine (10 days overdue): Rs{fine3:.2f}")
print("\n--- Returning JS Book (triggers reservation) ---")
library.return_book(loan4.id)
print("\n--- Borrowing Limit Test ---")
library.issue_book("M1", "978-0-201-63361-0")
library.issue_book("M1", "978-0-14-028329-7")
library.issue_book("M1", "978-0-06-112008-4")
try:
library.issue_book("M1", "978-0-13-468599-1")
except RuntimeError as e:
print(f" Expected: {e}")
print("\n--- Fine Strategy Comparison ---")
print(f" Per-day (Rs5): 10 days = Rs{PerDayFine(5).calculate_fine(10)}")
print(f" Tiered: 10 days = Rs{TieredFine().calculate_fine(10)}")
print(f" Flat (Rs50): 10 days = Rs{FlatFine(50).calculate_fine(10)}")
library.display_catalog()
library.display_members()
print("\nโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
print(" DEMO COMPLETE ")
print("โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ")
if __name__ == "__main__":
main()
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
#include <queue>
#include <mutex>
#include <memory>
#include <algorithm>
#include <iomanip>
#include <sstream>
#include <random>
#include <stdexcept>
// โโโ Enums โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
enum class MemberType { STANDARD, PREMIUM };
enum class BookStatus { AVAILABLE, ISSUED, RESERVED, LOST };
int maxBooks(MemberType t) { return t == MemberType::STANDARD ? 3 : 10; }
int loanDays(MemberType t) { return t == MemberType::STANDARD ? 14 : 30; }
std::string genLoanId() {
static std::mt19937 gen(std::random_device{}());
static std::uniform_int_distribution<int> dist(0, 15);
static const char* hex = "0123456789abcdef";
std::string id = "LOAN-";
for (int i = 0; i < 6; ++i) id += hex[dist(gen)];
return id;
}
// โโโ Simple Date (days since epoch for simplicity) โโโโโโ
struct SimpleDate {
int days; // days from some reference
SimpleDate(int d = 0) : days(d) {}
SimpleDate plusDays(int n) const { return {days + n}; }
bool isAfter(SimpleDate o) const { return days > o.days; }
int daysUntil(SimpleDate o) const { return o.days - days; }
std::string str() const { return "day-" + std::to_string(days); }
};
SimpleDate today() { return {100}; } // simulated "today"
// โโโ Book Copy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
struct BookCopy {
std::string copyId, isbn;
BookStatus status = BookStatus::AVAILABLE;
bool isAvailable() const { return status == BookStatus::AVAILABLE; }
void markIssued() { status = BookStatus::ISSUED; }
void markReturned() { status = BookStatus::AVAILABLE; }
};
// โโโ Book โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Book {
public:
std::string isbn, title, author, category;
std::vector<BookCopy> copies;
Book(std::string isbn, std::string title, std::string author,
std::string cat, int numCopies)
: isbn(std::move(isbn)), title(std::move(title)),
author(std::move(author)), category(std::move(cat)) {
for (int i = 1; i <= numCopies; ++i) {
copies.push_back({this->isbn + "-C" + std::to_string(i), this->isbn});
}
}
BookCopy* getAvailableCopy() {
for (auto& c : copies) if (c.isAvailable()) return &c;
return nullptr;
}
int availableCount() const {
int cnt = 0;
for (auto& c : copies) if (c.isAvailable()) cnt++;
return cnt;
}
std::string toString() const {
return "Book[" + title + " by " + author + " | " +
std::to_string(availableCount()) + "/" +
std::to_string(copies.size()) + " available]";
}
};
// โโโ Member โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Member {
public:
std::string id, name;
MemberType type;
std::vector<std::string> activeLoanIds;
Member(std::string id, std::string name, MemberType type)
: id(std::move(id)), name(std::move(name)), type(type) {}
bool canBorrow() const { return (int)activeLoanIds.size() < maxBooks(type); }
void addLoan(const std::string& lid) { activeLoanIds.push_back(lid); }
void removeLoan(const std::string& lid) {
activeLoanIds.erase(
std::remove(activeLoanIds.begin(), activeLoanIds.end(), lid),
activeLoanIds.end());
}
};
// โโโ Book Loan โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
struct BookLoan {
std::string id;
Member* member;
BookCopy* bookCopy;
std::string bookTitle;
SimpleDate issueDate, dueDate, returnDate;
double fineAmount = 0;
bool returned = false;
int daysOverdue(SimpleDate retDate) const {
if (retDate.isAfter(dueDate)) return retDate.days - dueDate.days;
return 0;
}
};
// โโโ Fine Strategy โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class FineStrategy {
public:
virtual ~FineStrategy() = default;
virtual double calculateFine(int daysOverdue) = 0;
};
class PerDayFine : public FineStrategy {
double rate;
public:
PerDayFine(double r) : rate(r) {}
double calculateFine(int days) override { return days * rate; }
};
class TieredFine : public FineStrategy {
public:
double calculateFine(int days) override {
if (days <= 0) return 0;
if (days <= 7) return days * 2.0;
if (days <= 14) return 14 + (days - 7) * 5.0;
return 14 + 35 + (days - 14) * 10.0;
}
};
class FlatFine : public FineStrategy {
double amount;
public:
FlatFine(double a) : amount(a) {}
double calculateFine(int days) override { return days > 0 ? amount : 0; }
};
// โโโ Reservation โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
struct Reservation {
Member* member;
std::string isbn;
};
// โโโ Library โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
class Library {
std::unordered_map<std::string, std::unique_ptr<Book>> catalog;
std::unordered_map<std::string, std::unique_ptr<Member>> members;
std::unordered_map<std::string, std::unique_ptr<BookLoan>> activeLoans;
std::unordered_map<std::string, std::queue<Reservation>> reservations;
std::shared_ptr<FineStrategy> fineStrategy;
std::mutex mtx;
public:
Library(std::shared_ptr<FineStrategy> fs) : fineStrategy(std::move(fs)) {}
void setFineStrategy(std::shared_ptr<FineStrategy> fs) { fineStrategy = std::move(fs); }
Book* addBook(std::string isbn, std::string title, std::string author,
std::string cat, int copies) {
auto book = std::make_unique<Book>(isbn, title, author, cat, copies);
Book* ptr = book.get();
catalog[isbn] = std::move(book);
return ptr;
}
Member* registerMember(std::string id, std::string name, MemberType type) {
auto m = std::make_unique<Member>(id, name, type);
Member* ptr = m.get();
members[id] = std::move(m);
return ptr;
}
BookLoan* issueBook(const std::string& memberId, const std::string& isbn) {
std::lock_guard<std::mutex> lock(mtx);
auto mIt = members.find(memberId);
if (mIt == members.end()) throw std::runtime_error("Member not found");
Member* member = mIt->second.get();
if (!member->canBorrow())
throw std::runtime_error(member->name + " reached borrowing limit");
auto bIt = catalog.find(isbn);
if (bIt == catalog.end()) throw std::runtime_error("Book not found");
Book* book = bIt->second.get();
BookCopy* copy = book->getAvailableCopy();
if (!copy) throw std::runtime_error("No copies available for: " + book->title);
copy->markIssued();
auto loan = std::make_unique<BookLoan>();
loan->id = genLoanId();
loan->member = member;
loan->bookCopy = copy;
loan->bookTitle = book->title;
loan->issueDate = today();
loan->dueDate = today().plusDays(loanDays(member->type));
BookLoan* ptr = loan.get();
member->addLoan(ptr->id);
activeLoans[ptr->id] = std::move(loan);
std::cout << " [ISSUED] " << book->title << " to " << member->name
<< " (due: " << ptr->dueDate.str() << ")\n";
return ptr;
}
double returnBook(const std::string& loanId, SimpleDate returnDate = today()) {
std::lock_guard<std::mutex> lock(mtx);
auto it = activeLoans.find(loanId);
if (it == activeLoans.end()) throw std::runtime_error("Loan not found");
BookLoan* loan = it->second.get();
int overdue = loan->daysOverdue(returnDate);
double fine = fineStrategy->calculateFine(overdue);
loan->returnDate = returnDate;
loan->fineAmount = fine;
loan->returned = true;
loan->bookCopy->markReturned();
loan->member->removeLoan(loanId);
std::string fineStr = fine > 0 ?
" | Fine: Rs" + std::to_string((int)fine) : "";
std::cout << " [RETURNED] " << loan->bookTitle << " by "
<< loan->member->name << fineStr << "\n";
// Check reservations
auto rIt = reservations.find(loan->bookCopy->isbn);
if (rIt != reservations.end() && !rIt->second.empty()) {
Reservation res = rIt->second.front();
rIt->second.pop();
std::cout << " [RESERVED] " << loan->bookTitle
<< " now available for " << res.member->name << "\n";
}
activeLoans.erase(loanId);
return fine;
}
void reserveBook(const std::string& memberId, const std::string& isbn) {
std::lock_guard<std::mutex> lock(mtx);
Member* member = members[memberId].get();
Book* book = catalog[isbn].get();
if (book->availableCount() > 0)
throw std::runtime_error("Book available - issue directly");
reservations[isbn].push({member, isbn});
std::cout << " " << member->name << " reserved '" << book->title << "'\n";
}
void displayCatalog() {
std::cout << "\n--- Library Catalog ---\n";
for (auto& [_, b] : catalog) std::cout << " " << b->toString() << "\n";
}
};
// โโโ Main Demo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
int main() {
std::cout << "=======================================\n";
std::cout << " LIBRARY MANAGEMENT - LLD DEMO \n";
std::cout << "=======================================\n\n";
Library library(std::make_shared<TieredFine>());
std::cout << "--- Adding Books ---\n";
library.addBook("ISBN-001", "Clean Code", "Robert Martin", "Programming", 3);
library.addBook("ISBN-002", "Design Patterns", "Gang of Four", "Programming", 2);
library.addBook("ISBN-003", "JS Good Parts", "Douglas Crockford", "Programming", 1);
library.addBook("ISBN-004", "Mockingbird", "Harper Lee", "Fiction", 4);
library.displayCatalog();
std::cout << "\n--- Registering Members ---\n";
library.registerMember("M1", "Rahul", MemberType::STANDARD);
library.registerMember("M2", "Priya", MemberType::PREMIUM);
library.registerMember("M3", "Arjun", MemberType::STANDARD);
std::cout << "\n--- Issuing Books ---\n";
auto* loan1 = library.issueBook("M1", "ISBN-001");
auto* loan2 = library.issueBook("M2", "ISBN-001");
auto* loan3 = library.issueBook("M1", "ISBN-004");
auto* loan4 = library.issueBook("M3", "ISBN-003");
std::cout << "\n--- Reservation ---\n";
try { library.issueBook("M2", "ISBN-003"); }
catch (const std::runtime_error& e) { std::cout << " " << e.what() << "\n"; }
library.reserveBook("M2", "ISBN-003");
std::cout << "\n--- Returning (on time) ---\n";
library.returnBook(loan1->id);
std::cout << "\n--- Returning Overdue (10 days late) ---\n";
SimpleDate overdueDate = loan3->dueDate.plusDays(10);
double fine = library.returnBook(loan3->id, overdueDate);
std::cout << " Fine (10 days): Rs" << fine << "\n";
std::cout << "\n--- Return JS Book (triggers reservation) ---\n";
library.returnBook(loan4->id);
std::cout << "\n--- Borrowing Limit Test ---\n";
library.issueBook("M1", "ISBN-002");
library.issueBook("M1", "ISBN-004");
library.issueBook("M1", "ISBN-001");
try { library.issueBook("M1", "ISBN-002"); }
catch (const std::runtime_error& e) { std::cout << " Expected: " << e.what() << "\n"; }
std::cout << "\n--- Fine Comparison ---\n";
std::cout << " PerDay(Rs5): 10 days = Rs" << PerDayFine(5).calculateFine(10) << "\n";
std::cout << " Tiered: 10 days = Rs" << TieredFine().calculateFine(10) << "\n";
std::cout << " Flat(Rs50): 10 days = Rs" << FlatFine(50).calculateFine(10) << "\n";
library.displayCatalog();
std::cout << "\n=======================================\n";
std::cout << " DEMO COMPLETE \n";
std::cout << "=======================================\n";
return 0;
}
State Transitions
stateDiagram-v2
[*] --> AVAILABLE
AVAILABLE --> ISSUED : member borrows
ISSUED --> AVAILABLE : returned on time
ISSUED --> OVERDUE : past due date
OVERDUE --> AVAILABLE : returned with fine
AVAILABLE --> RESERVED : reservation exists
RESERVED --> ISSUED : reserved member borrows
ISSUED --> LOST : reported lost
LOST --> [*]
Sequence Diagram - Issue and Return
sequenceDiagram
participant Mem as Member
participant Lib as Library
participant Book
participant Copy as BookCopy
participant FS as FineStrategy
participant Obs as Observer
Mem->>Lib: issueBook(memberId, isbn)
Lib->>Lib: check member can borrow
Lib->>Book: getAvailableCopy()
Book-->>Lib: BookCopy
Lib->>Copy: markIssued()
Lib->>Lib: create BookLoan
Lib->>Obs: onBookIssued(loan)
Lib-->>Mem: BookLoan
Note over Mem,Lib: Time passes...
Mem->>Lib: returnBook(loanId)
Lib->>Lib: find loan
Lib->>FS: calculateFine(daysOverdue)
FS-->>Lib: fine amount
Lib->>Copy: markReturned()
Lib->>Lib: check reservations
Lib->>Obs: onBookReturned(loan)
Lib-->>Mem: fine amount
How to Extend
| Extension | Implementation |
|---|---|
| E-books | New DigitalBook subclass with no physical copies; unlimited simultaneous borrows |
| Inter-library loans | Bridge pattern connecting multiple Library instances |
| Barcode/QR scanning | Map barcode โ copyId for quick check-in/check-out |
| Membership renewal | Add expiry date to Member; reject borrows if expired |
| Book recommendations | Track borrow history โ collaborative filtering |
| SMS/email notifications | Multiple observer implementations for different channels |
| Late return reminders | Scheduled task checks overdue loans daily, notifies members |
What Interviewers Look For
- โ Strategy pattern for fine calculation - multiple algorithms swappable
- โ Reservation queue with FIFO auto-promotion
- โ Borrowing limits per member type
- โ Thread-safety on issue/return
- โ Observer pattern for notifications
- โ Book copies vs Book entity (one book can have multiple physical copies)
- โ Overdue tracking with configurable fine computation
- โ Search by title, author, ISBN
Related Concepts
Scale this design past a single process and these are the concepts it runs into:
- Database Indexing โ โ search by title, author and ISBN is exactly what secondary indexes exist for
- Message Queues โ โ overdue notices and reservation-available alerts are async jobs, not work done inside issue and return
- Distributed Locking โ โ issuing the last available copy is a contended read-modify-write across replicas
- Caching โ โ catalog reads vastly outnumber issue and return writes, so the catalog is the obvious cache tier
Discussion
Newest first