Functional Scope (In-Scope)
- Dynamic Book Search: Filter books by Title, Author, or ISBN index configurations.
- Checkout and Return: Support checkout and return pipelines including automatic penalty fine audits.
- FIFO Book Reservations: Per-book reservation queues holding copies for waiting list members in FIFO order.
- Outstanding Fine Blocks: Block member book checkouts if total unpaid fines exceed configurable limits (e.g. $50).
Explicit Boundaries (Out-of-Scope)
- No Real Physical Locker Interfacing: Hardware scanners, shelf location tags, or physical locker triggers are mocked behind clean API classes.
- No External Payment Gateway: Overdue fine payments are resolved internally on account sheets rather than integrating with card processors.
Highly robust reference designs in Java and Python:
// ─── JAVA BLUEPRINT ──────────────────────────────────────────────────────────
import java.util.*;
import java.util.concurrent.*;
enum BookStatus { AVAILABLE, LOANED, RESERVED }
interface PenaltyStrategy {
double calculateFine(long daysOverdue);
}
class StandardPenaltyStrategy implements PenaltyStrategy {
private final double dailyRate;
public StandardPenaltyStrategy(double dailyRate) { this.dailyRate = dailyRate; }
@Override
public double calculateFine(long daysOverdue) {
return daysOverdue * dailyRate;
}
}
class PremiumPenaltyStrategy implements PenaltyStrategy {
private final double dailyRate;
public PremiumPenaltyStrategy(double dailyRate) { this.dailyRate = dailyRate; }
@Override
public double calculateFine(long daysOverdue) {
if (daysOverdue <= 5) {
return daysOverdue * dailyRate;
}
return (5 * dailyRate) + ((daysOverdue - 5) * dailyRate * 2.0); // Double fine after 5 days
}
}
class Book {
private final String isbn;
private final String title;
private final String author;
private BookStatus status = BookStatus.AVAILABLE;
private final Queue<String> reservationQueue = new LinkedList<>();
public Book(String isbn, String title, String author) {
this.isbn = isbn;
this.title = title;
this.author = author;
}
public String getIsbn() { return isbn; }
public String getTitle() { return title; }
public String getAuthor() { return author; }
public synchronized BookStatus getStatus() { return status; }
public synchronized void setStatus(BookStatus status) { this.status = status; }
public synchronized void reserveMember(String memberId) {
reservationQueue.add(memberId);
if (status == BookStatus.AVAILABLE) {
status = BookStatus.RESERVED;
}
}
public synchronized String pollReservation() {
return reservationQueue.poll();
}
public synchronized boolean hasReservations() {
return !reservationQueue.isEmpty();
}
}
class Loan {
private final String bookIsbn;
private final String memberId;
private final long issueDate;
private final long dueDate;
private long returnDate = 0;
public Loan(String bookIsbn, String memberId, long issueDate, long dueDate) {
this.bookIsbn = bookIsbn;
this.memberId = memberId;
this.issueDate = issueDate;
this.dueDate = dueDate;
}
public String getBookIsbn() { return bookIsbn; }
public String getMemberId() { return memberId; }
public long getDueDate() { return dueDate; }
public void setReturnDate(long returnDate) { this.returnDate = returnDate; }
public double calculateFine(long now, PenaltyStrategy strategy) {
long effectiveEnd = (returnDate > 0) ? returnDate : now;
if (effectiveEnd > dueDate) {
long daysOverdue = (effectiveEnd - dueDate) / (1000 * 60 * 60 * 24);
return strategy.calculateFine(daysOverdue);
}
return 0.0;
}
}
class Member {
private final String id;
private final String name;
private final List<Loan> activeLoans = new ArrayList<>();
private double unpaidFines = 0.0;
public Member(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() { return id; }
public synchronized List<Loan> getActiveLoans() { return new ArrayList<>(activeLoans); }
public synchronized void addLoan(Loan loan) { activeLoans.add(loan); }
public synchronized void removeLoan(Loan loan) { activeLoans.remove(loan); }
public synchronized double getUnpaidFines() { return unpaidFines; }
public synchronized void addFine(double amount) { unpaidFines += amount; }
}
class LibraryService {
private final Map<String, Book> books = new ConcurrentHashMap<>();
private final Map<String, Member> members = new ConcurrentHashMap<>();
private final PenaltyStrategy penaltyStrategy;
public LibraryService(PenaltyStrategy penaltyStrategy) {
this.penaltyStrategy = penaltyStrategy;
}
public void addBook(Book book) { books.put(book.getIsbn(), book); }
public void addMember(Member member) { members.put(member.getId(), member); }
public boolean checkoutBook(String memberId, String isbn) {
Book book = books.get(isbn);
Member member = members.get(memberId);
if (book == null || member == null) return false;
if (member.getUnpaidFines() > 50.0) {
System.out.println("Checkout rejected: Member " + member.getId() + " has excess fines of $" + member.getUnpaidFines());
return false;
}
synchronized (book) {
if (book.getStatus() == BookStatus.AVAILABLE) {
book.setStatus(BookStatus.LOANED);
} else if (book.getStatus() == BookStatus.RESERVED) {
String nextInLine = book.pollReservation();
if (memberId.equals(nextInLine)) {
book.setStatus(BookStatus.LOANED);
} else {
return false;
}
} else {
return false;
}
long now = System.currentTimeMillis();
long dueDate = now + (14L * 24 * 60 * 60 * 1000);
Loan loan = new Loan(isbn, memberId, now, dueDate);
member.addLoan(loan);
System.out.println("Checkout successful: " + book.getTitle() + " lent to Member " + member.getId());
return true;
}
}
public void reserveBook(String memberId, String isbn) {
Book book = books.get(isbn);
Member member = members.get(memberId);
if (book == null || member == null) return;
book.reserveMember(memberId);
System.out.println("Reservation successful: " + book.getTitle() + " reserved by Member " + member.getId());
}
public void returnBook(String memberId, String isbn, long returnTimeOverride) {
Book book = books.get(isbn);
Member member = members.get(memberId);
if (book == null || member == null) return;
Loan targetLoan = null;
for (Loan l : member.getActiveLoans()) {
if (l.getBookIsbn().equals(isbn)) {
targetLoan = l;
break;
}
}
if (targetLoan == null) return;
targetLoan.setReturnDate(returnTimeOverride);
double fine = targetLoan.calculateFine(returnTimeOverride, penaltyStrategy);
if (fine > 0) {
member.addFine(fine);
System.out.println("Overdue Book returned! Fine calculated: $" + fine);
} else {
System.out.println("Book returned in time. No fine.");
}
member.removeLoan(targetLoan);
synchronized (book) {
if (book.hasReservations()) {
book.setStatus(BookStatus.RESERVED);
} else {
book.setStatus(BookStatus.AVAILABLE);
}
}
}
}
public class LibraryDriver {
public static void main(String[] args) {
System.out.println("=== LIBRARY MANAGEMENT SYSTEM SIMULATION ===");
PenaltyStrategy strategy = new PremiumPenaltyStrategy(2.0); // $2.0 per day, double after 5 days
LibraryService library = new LibraryService(strategy);
Book b1 = new Book("ISBN-11", "Clean Architecture", "Robert C. Martin");
library.addBook(b1);
Member m1 = new Member("MEM-1", "Alice");
Member m2 = new Member("MEM-2", "Bob");
library.addMember(m1);
library.addMember(m2);
// Checkout success
library.checkoutBook("MEM-1", "ISBN-11");
// Bob reserves the book while Alice has it
library.reserveBook("MEM-2", "ISBN-11");
// Alice returns it overdue by 8 days
long now = System.currentTimeMillis();
long eightDaysLater = now + (22L * 24 * 60 * 60 * 1000); // 14 days due + 8 days late
library.returnBook("MEM-1", "ISBN-11", eightDaysLater);
System.out.println("Alice unpaid fines: $" + m1.getUnpaidFines()); // 5 * 2 + 3 * 4 = $22.0
}
}