OOD - Library Management System
Problem Statement
Design a comprehensive library management system that efficiently handles all aspects of library operations including book catalog management, member management, lending operations, reservations, fine collection, and notifications.
The system must support:
- Catalog Management: Add, update, remove books with multiple copies (book items). Search by title, author, ISBN, subject, publication date with advanced filtering and sorting
- Membership System: Multiple membership tiers (Basic, Premium, Gold) with different borrowing privileges, limits, and fee structures
- Lending Operations: Check out, return, and renew book items with proper validation of borrowing limits, due dates, and reservation queues
- Reservation System: Allow members to reserve currently unavailable books with FIFO queue management and automatic notifications when books become available
- Fine Management: Calculate and collect fines for overdue books with configurable rates, grace periods, and membership-specific policies
- Notification System: Multi-channel notifications (email, SMS, push) for events like due date reminders, reservation availability, overdue warnings, and hold expirations
- Administrative Functions: Librarian operations for member management, catalog updates, fine waivers, and reporting (popular books, overdue items, revenue)
- Book Item Tracking: Track individual copies with barcode, condition (new, good, worn, damaged), location (rack, shelf), and status (available, checked out, reserved, lost, under repair)
- Special Collections: Support for reference-only books, rare books, and media types (audiobooks, ebooks, DVDs, magazines)
Requirements Analysis
Functional Requirements
Core Operations:
-
Catalog Management
- Add new books with metadata (ISBN, title, author, publisher, subject, publication date)
- Add multiple physical copies (book items) with unique barcodes
- Update book information and copy status
- Remove books and track removed copies
- Tag books (fiction, non-fiction, bestseller, award-winner)
-
Search and Discovery
- Search by title, author, ISBN, subject, tags with fuzzy matching
- Filter by format (hardcover, paperback, ebook, audiobook)
- Filter by availability status
- Sort by relevance, publication date, popularity
- Advanced search combining multiple criteria
-
Member Management
- Register new members with membership tiers
- Upgrade/downgrade memberships
- Track borrowing history and preferences
- Block members for violations (excessive fines, lost books)
- Set borrowing limits based on membership level
-
Checkout Operations
- Validate member eligibility (active account, not exceeded limit, no excessive fines)
- Check book availability (not reference-only, not already checked out)
- Calculate due date based on book type and membership
- Generate checkout receipt
- Update book item status and member's checked-out count
-
Return Operations
- Scan returned book item
- Calculate fines if overdue
- Update book status to available
- Check reservation queue and notify next member
- Generate return receipt
-
Renewal Operations
- Allow renewal if no reservations pending
- Limit renewals (max 2 times)
- Extend due date
- Prevent renewal for overdue items with excessive fines
-
Reservation System
- Place reservation for currently unavailable books
- Maintain FIFO queue for multiple reservations
- Notify member when reserved book available
- Hold book for 48 hours after notification
- Auto-cancel reservation if not picked up
- Allow reservation cancellation
-
Fine Calculation
- Daily fine rate varies by membership tier
- Grace period (1-3 days depending on membership)
- Maximum fine cap per book
- Waive fines for special circumstances
- Track fine payment history
-
Notifications
- Due date reminder (3 days before, 1 day before)
- Overdue notice (1 day after, 7 days after)
- Reservation available notification
- Reservation expiring notification (24 hours before hold expiry)
- Account suspension notice
Non-Functional Requirements
- Performance: Catalog search < 500ms for 100K+ books, checkout/return < 2 seconds
- Scalability: Support 10K+ members, 100K+ books, 1K+ concurrent operations
- Reliability: Zero data loss for transactions, consistent state across distributed operations
- Availability: 99.9% uptime, graceful degradation if notification service fails
- Security: Secure member authentication, role-based access (member vs librarian), audit logging
- Usability: Intuitive search interface, clear error messages, accessibility compliance
Use Case Diagram
graph TB subgraph Actors Member[๐ค Member] Librarian[๐ Librarian] System[๐ฅ๏ธ System] end subgraph "Member Use Cases" UC1[๐ Search Catalog] UC2[๐ Checkout Book] UC3[๐ Return Book] UC4[๐ Renew Book] UC5[๐ Reserve Book] UC6[โ Cancel Reservation] UC7[๐ณ Pay Fine] UC8[๐ View Borrowing History] UC9[๐ View Account Status] end subgraph "Librarian Use Cases" UC10[โ Add Book to Catalog] UC11[โ๏ธ Update Book Information] UC12[๐๏ธ Remove Book] UC13[๐ฅ Register Member] UC14[๐ง Manage Member Account] UC15[๐ฐ Waive Fine] UC16[๐ Generate Reports] UC17[๐ท๏ธ Mark Book Lost/Damaged] UC18[โ Issue Replacement Card] end subgraph "System Use Cases" UC19[๐ Send Notifications] UC20[โ ๏ธ Calculate Fines] UC21[๐ข Process Reservation Queue] UC22[๐ซ Auto-Cancel Expired Holds] end Member --> UC1 Member --> UC2 Member --> UC3 Member --> UC4 Member --> UC5 Member --> UC6 Member --> UC7 Member --> UC8 Member --> UC9 Librarian --> UC10 Librarian --> UC11 Librarian --> UC12 Librarian --> UC13 Librarian --> UC14 Librarian --> UC15 Librarian --> UC16 Librarian --> UC17 Librarian --> UC18 Librarian -.-> UC1 System --> UC19 System --> UC20 System --> UC21 System --> UC22 style UC1 fill:#e1f5ff,stroke:#01579b,stroke-width:2px style UC2 fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style UC3 fill:#fff9c4,stroke:#f57f17,stroke-width:2px style UC4 fill:#f3e5f5,stroke:#6a1b9a,stroke-width:2px style UC5 fill:#ffe0b2,stroke:#e65100,stroke-width:2px style UC6 fill:#ffcdd2,stroke:#c62828,stroke-width:2px style UC7 fill:#b2dfdb,stroke:#00695c,stroke-width:2px style UC8 fill:#d1c4e9,stroke:#4527a0,stroke-width:2px style UC9 fill:#b2ebf2,stroke:#006064,stroke-width:2px style UC10 fill:#c5e1a5,stroke:#558b2f,stroke-width:2px style UC11 fill:#ffccbc,stroke:#bf360c,stroke-width:2px style UC12 fill:#ffab91,stroke:#d84315,stroke-width:2px style UC13 fill:#ce93d8,stroke:#6a1b9a,stroke-width:2px style UC14 fill:#ffe082,stroke:#f9a825,stroke-width:2px style UC15 fill:#a5d6a7,stroke:#388e3c,stroke-width:2px style UC16 fill:#90caf9,stroke:#1565c0,stroke-width:2px style UC17 fill:#ef9a9a,stroke:#c62828,stroke-width:2px style UC18 fill:#b39ddb,stroke:#5e35b1,stroke-width:2px style UC19 fill:#80deea,stroke:#00695c,stroke-width:2px style UC20 fill:#ffcc80,stroke:#ef6c00,stroke-width:2px style UC21 fill:#bcaaa4,stroke:#5d4037,stroke-width:2px style UC22 fill:#f48fb1,stroke:#ad1457,stroke-width:2px
Class Diagram
The design demonstrates multiple key patterns:
- Strategy Pattern: FineCalculationStrategy for flexible fine policies
- Observer Pattern: NotificationService observers for multi-channel notifications
- State Pattern: BookItemState for book lifecycle management
- Command Pattern: Library operations (CheckoutCommand, ReturnCommand, etc.)
- Composite Pattern: SearchCriteria for complex catalog queries
classDiagram class Library { -String name -Address address -Catalog catalog -List~Member~ members -List~Librarian~ librarians -NotificationService notificationService -ReservationManager reservationManager +addBook(book) +removeBook(isbn) +registerMember(member) +getAvailableBooks() List~BookItem~ } class Catalog { -Map~String,Book~ booksByISBN -Map~String,List~Book~~ booksByTitle -Map~String,List~Book~~ booksByAuthor -Map~String,List~Book~~ booksBySubject +addBook(book) +removeBook(isbn) +search(criteria) List~Book~ +getBookByISBN(isbn) Book } class SearchCriteria { <<interface>> +matches(book) boolean } class TitleSearchCriteria { -String title +matches(book) boolean } class AuthorSearchCriteria { -String authorName +matches(book) boolean } class CompositeSearchCriteria { -List~SearchCriteria~ criteria -SearchOperator operator +matches(book) boolean } class Book { -String isbn -String title -List~Author~ authors -String publisher -String subject -LocalDate publicationDate -int numberOfPages -List~String~ tags -List~BookItem~ items +addBookItem(item) +getAvailableItems() List~BookItem~ +getTotalCopies() int } class Author { -String name -String biography -List~Book~ books } class BookItem { -String barcode -Book book -BookFormat format -BookItemState state -BookCondition condition -double price -LocalDate purchasedDate -String rackLocation -boolean isReferenceOnly +isAvailable() boolean +checkout(member) +returnItem() +reserve(member) +setState(state) } class BookFormat { <<enumeration>> HARDCOVER PAPERBACK EBOOK AUDIOBOOK MAGAZINE DVD } class BookItemState { <<interface>> +checkout(item, member) +returnItem(item) +reserve(item, member) } class AvailableState { +checkout(item, member) +returnItem(item) +reserve(item, member) } class CheckedOutState { +checkout(item, member) +returnItem(item) +reserve(item, member) } class ReservedState { +checkout(item, member) +returnItem(item) +reserve(item, member) } class LostState { +checkout(item, member) +returnItem(item) +reserve(item, member) } class BookCondition { <<enumeration>> NEW GOOD FAIR WORN DAMAGED } class Account { <<abstract>> -String accountId -String username -String password -Person person -AccountStatus status +resetPassword() +getAccountStatus() AccountStatus } class Member { -String memberId -MembershipTier tier -LocalDate membershipDate -int totalBooksCheckedOut -double outstandingFines -List~BookLending~ activeLendings -List~Reservation~ activeReservations -BorrowingHistory history +canCheckout() boolean +getTotalFines() double +getBorrowingLimit() int +checkout(bookItem) +returnBook(bookItem) +renewBook(lending) +reserveBook(book) } class MembershipTier { <<enumeration>> BASIC PREMIUM GOLD } class Librarian { -String employeeId +addBookToCatalog(book) +removeBookFromCatalog(isbn) +registerMember(member) +blockMember(memberId) +waiveFine(memberId, amount) +generateReport(type) Report } class Person { -String name -String email -String phone -Address address } class AccountStatus { <<enumeration>> ACTIVE SUSPENDED CLOSED BLACKLISTED } class BookLending { -String lendingId -BookItem bookItem -Member member -LocalDateTime checkoutDate -LocalDateTime dueDate -LocalDateTime returnDate -int renewalCount -LendingStatus status +renew() +returnBook() +isOverdue() boolean +getDaysOverdue() int +calculateFine() Money } class LendingStatus { <<enumeration>> ACTIVE RETURNED OVERDUE LOST } class Reservation { -String reservationId -Book book -Member member -LocalDateTime createdDate -LocalDateTime notifiedDate -LocalDateTime expiryDate -ReservationStatus status -int queuePosition +cancel() +fulfill(bookItem) +isExpired() boolean } class ReservationStatus { <<enumeration>> PENDING NOTIFIED COMPLETED CANCELLED EXPIRED } class ReservationManager { -Map~String,Queue~Reservation~~ reservationQueues +addReservation(reservation) +cancelReservation(reservationId) +getNextReservation(book) Reservation +notifyMember(reservation) +processQueue(book) } class FineCalculationStrategy { <<interface>> +calculateFine(daysOverdue, tier) Money } class StandardFineStrategy { -Map~MembershipTier,Money~ dailyRates -Map~MembershipTier,Integer~ gracePeriods -Money maxFinePerBook +calculateFine(daysOverdue, tier) Money } class GracePeriodFineStrategy { -FineCalculationStrategy baseStrategy -int graceDays +calculateFine(daysOverdue, tier) Money } class Fine { -String fineId -Member member -BookLending lending -Money amount -LocalDateTime createdDate -LocalDateTime paidDate -FineStatus status +pay() +waive() } class FineStatus { <<enumeration>> PENDING PAID WAIVED } class NotificationService { -List~NotificationChannel~ channels +registerChannel(channel) +notify(member, notification) +sendDueDateReminder(lending) +sendOverdueNotice(lending) +sendReservationAvailable(reservation) } class NotificationChannel { <<interface>> +send(recipient, message) } class EmailNotification { -String smtpServer +send(recipient, message) } class SMSNotification { -String twilioApiKey +send(recipient, message) } class PushNotification { -String firebaseKey +send(recipient, message) } class Notification { -String notificationId -Member recipient -NotificationType type -String message -LocalDateTime sentDate -boolean isRead } class NotificationType { <<enumeration>> DUE_DATE_REMINDER OVERDUE_NOTICE RESERVATION_AVAILABLE RESERVATION_EXPIRING ACCOUNT_SUSPENDED } class LibraryCommand { <<interface>> +execute() +undo() } class CheckoutCommand { -Member member -BookItem bookItem -Library library +execute() +undo() } class ReturnCommand { -BookLending lending -Library library +execute() +undo() } class RenewCommand { -BookLending lending +execute() +undo() } Library "1" *-- "1" Catalog Library "1" *-- "many" Member Library "1" *-- "many" Librarian Library "1" --> "1" NotificationService Library "1" --> "1" ReservationManager Catalog "1" *-- "many" Book Catalog --> SearchCriteria SearchCriteria <|.. TitleSearchCriteria SearchCriteria <|.. AuthorSearchCriteria SearchCriteria <|.. CompositeSearchCriteria Book "1" *-- "many" BookItem Book "many" --> "many" Author BookItem --> BookFormat BookItem --> BookCondition BookItem --> BookItemState BookItemState <|.. AvailableState BookItemState <|.. CheckedOutState BookItemState <|.. ReservedState BookItemState <|.. LostState Account <|-- Member Account <|-- Librarian Account --> Person Account --> AccountStatus Member --> MembershipTier Member "1" *-- "many" BookLending Member "1" *-- "many" Reservation BookLending --> BookItem BookLending --> Member BookLending --> LendingStatus Reservation --> Book Reservation --> Member Reservation --> ReservationStatus ReservationManager "1" *-- "many" Reservation FineCalculationStrategy <|.. StandardFineStrategy FineCalculationStrategy <|.. GracePeriodFineStrategy Fine --> Member Fine --> BookLending Fine --> FineStatus NotificationService "1" --> "many" NotificationChannel NotificationChannel <|.. EmailNotification NotificationChannel <|.. SMSNotification NotificationChannel <|.. PushNotification Notification --> Member Notification --> NotificationType LibraryCommand <|.. CheckoutCommand LibraryCommand <|.. ReturnCommand LibraryCommand <|.. RenewCommand
Activity Diagrams
1. Book Checkout Flow
flowchart TD Start([๐ค Member Searches Catalog]) --> Search[๐ Search for Book<br/>by Title/Author/ISBN] Search --> SelectBook[๐ Select Desired Book] SelectBook --> CheckCopies{Available<br/>Copies?} CheckCopies -->|No| Reserve[๐ Place Reservation<br/>Join Wait Queue] Reserve --> NotifyQueue[๐ง Notify Member of<br/>Queue Position] NotifyQueue --> End1([โณ Waiting for Availability]) CheckCopies -->|Yes| CheckReference{Reference<br/>Only?} CheckReference -->|Yes| DenyRef[โ Cannot Check Out<br/>Reference Books] DenyRef --> End2([๐ซ Checkout Denied]) CheckReference -->|No| CheckAccount{Member<br/>Account Active?} CheckAccount -->|No| DenyAccount[โ Account Suspended<br/>Contact Librarian] DenyAccount --> End2 CheckAccount -->|Yes| CheckFines{Outstanding<br/>Fines > $10?} CheckFines -->|Yes| PayFines[๐ฐ Must Pay Fines First] PayFines --> End2 CheckFines -->|No| CheckLimit{Reached<br/>Borrowing Limit?} CheckLimit -->|Yes| DenyLimit[โ Maximum Books<br/>Already Checked Out] DenyLimit --> End2 CheckLimit -->|No| CreateLending[โ Create BookLending Record<br/>Generate Lending ID] CreateLending --> CalcDueDate[๐ Calculate Due Date<br/>Based on Membership Tier] CalcDueDate --> UpdateItem[๐ Update BookItem State<br/>to CHECKED_OUT] UpdateItem --> UpdateMember[๐ค Increment Member's<br/>Checked Out Count] UpdateMember --> GenerateReceipt[๐งพ Generate Checkout Receipt<br/>with Due Date] GenerateReceipt --> SendConfirm[๐ง Send Checkout Confirmation<br/>Email/SMS] SendConfirm --> ScheduleReminder[โฐ Schedule Due Date<br/>Reminder Notifications] ScheduleReminder --> End3([โ Checkout Complete]) style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style Search fill:#b2dfdb,stroke:#00695c,stroke-width:2px style SelectBook fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style CheckCopies fill:#fff9c4,stroke:#f57f17,stroke-width:2px style Reserve fill:#ffe0b2,stroke:#e65100,stroke-width:2px style CheckReference fill:#fff59d,stroke:#fbc02d,stroke-width:2px style CheckAccount fill:#e1bee7,stroke:#8e24aa,stroke-width:2px style CheckFines fill:#ffccbc,stroke:#d84315,stroke-width:2px style CheckLimit fill:#ffab91,stroke:#d84315,stroke-width:2px style CreateLending fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style CalcDueDate fill:#bbdefb,stroke:#1976d2,stroke-width:2px style UpdateItem fill:#dcedc8,stroke:#558b2f,stroke-width:2px style UpdateMember fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style GenerateReceipt fill:#c5cae9,stroke:#3949ab,stroke-width:2px style SendConfirm fill:#b2ebf2,stroke:#006064,stroke-width:2px style ScheduleReminder fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style DenyRef fill:#ffcdd2,stroke:#c62828,stroke-width:2px style DenyAccount fill:#ffcdd2,stroke:#c62828,stroke-width:2px style PayFines fill:#ffe082,stroke:#f9a825,stroke-width:2px style DenyLimit fill:#ffcdd2,stroke:#c62828,stroke-width:2px style End1 fill:#ff8a65,stroke:#d84315,stroke-width:3px style End2 fill:#ef5350,stroke:#c62828,stroke-width:3px style End3 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
2. Book Return and Fine Processing Flow
flowchart TD Start([๐ Member Returns Book]) --> ScanBarcode[๐ Scan Book Barcode] ScanBarcode --> ValidateItem{Book Item<br/>Found?} ValidateItem -->|No| InvalidItem[โ Invalid Barcode<br/>Contact Librarian] InvalidItem --> End1([๐ซ Return Failed]) ValidateItem -->|Yes| FindLending[๐ Find Active Lending<br/>Record for Member] FindLending --> LendingFound{Lending<br/>Record Found?} LendingFound -->|No| NotCheckedOut[โ Book Not Checked Out<br/>to This Member] NotCheckedOut --> End1 LendingFound -->|Yes| CheckOverdue{Is<br/>Overdue?} CheckOverdue -->|No| NoFine[โ Return On Time<br/>No Fine] NoFine --> UpdateLending[๐ Update Lending Status<br/>to RETURNED] CheckOverdue -->|Yes| CalcDays[๐ Calculate Days Overdue] CalcDays --> CheckGrace{Within Grace<br/>Period?} CheckGrace -->|Yes| NoFine CheckGrace -->|No| ApplyTier[๐ค Get Member Tier<br/>BASIC/PREMIUM/GOLD] ApplyTier --> CalcFine[๐ฐ Calculate Fine<br/>Using FineCalculationStrategy] CalcFine --> ApplyCap[๐ Apply Maximum Fine Cap<br/>Per Book] ApplyCap --> CreateFine[๐ณ Create Fine Record<br/>Status: PENDING] CreateFine --> AddToAccount[๐ค Add Fine to Member's<br/>Outstanding Balance] AddToAccount --> CheckLimit{Total Fines<br/>> $50?} CheckLimit -->|Yes| SuspendAccount[๐ซ Suspend Member Account<br/>Send Suspension Notice] SuspendAccount --> UpdateLending CheckLimit -->|No| SendFineNotice[๐ง Send Fine Payment<br/>Notice via Email/SMS] SendFineNotice --> UpdateLending UpdateLending --> UpdateItem[๐ Update BookItem State<br/>to AVAILABLE] UpdateItem --> DecrementCount[๐ค Decrement Member's<br/>Checked Out Count] DecrementCount --> CheckReservations{Reservations<br/>in Queue?} CheckReservations -->|No| UpdateCatalog[๐ Update Catalog<br/>Availability Display] UpdateCatalog --> GenerateReceipt[๐งพ Generate Return Receipt] CheckReservations -->|Yes| GetNextRes[๐ Get Next Reservation<br/>from Queue (FIFO)] GetNextRes --> UpdateResItem[๐ Update BookItem State<br/>to RESERVED] UpdateResItem --> NotifyMember[๐ง Notify Reserved Member<br/>Book Available for Pickup] NotifyMember --> SetHoldExpiry[โฐ Set Hold Expiry<br/>48 Hours from Now] SetHoldExpiry --> UpdateResStatus[๐ Update Reservation Status<br/>to NOTIFIED] UpdateResStatus --> GenerateReceipt GenerateReceipt --> LogTransaction[๐ Log Return Transaction<br/>in Audit Trail] LogTransaction --> End2([โ Return Complete]) style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style ScanBarcode fill:#b2dfdb,stroke:#00695c,stroke-width:2px style ValidateItem fill:#fff9c4,stroke:#f57f17,stroke-width:2px style FindLending fill:#e1bee7,stroke:#8e24aa,stroke-width:2px style LendingFound fill:#fff59d,stroke:#fbc02d,stroke-width:2px style CheckOverdue fill:#ffe082,stroke:#f9a825,stroke-width:2px style NoFine fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style CalcDays fill:#ffccbc,stroke:#d84315,stroke-width:2px style CheckGrace fill:#fff9c4,stroke:#f57f17,stroke-width:2px style ApplyTier fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style CalcFine fill:#ffab91,stroke:#d84315,stroke-width:2px style ApplyCap fill:#ffe0b2,stroke:#e65100,stroke-width:2px style CreateFine fill:#ffcdd2,stroke:#c62828,stroke-width:2px style AddToAccount fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style CheckLimit fill:#fff59d,stroke:#fbc02d,stroke-width:2px style SuspendAccount fill:#ef9a9a,stroke:#c62828,stroke-width:2px style SendFineNotice fill:#ce93d8,stroke:#6a1b9a,stroke-width:2px style UpdateLending fill:#bbdefb,stroke:#1976d2,stroke-width:2px style UpdateItem fill:#dcedc8,stroke:#558b2f,stroke-width:2px style DecrementCount fill:#b2ebf2,stroke:#006064,stroke-width:2px style CheckReservations fill:#fff9c4,stroke:#f57f17,stroke-width:2px style GetNextRes fill:#ffe0b2,stroke:#e65100,stroke-width:2px style UpdateResItem fill:#c5e1a5,stroke:#558b2f,stroke-width:2px style NotifyMember fill:#b3e5fc,stroke:#0277bd,stroke-width:2px style SetHoldExpiry fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style UpdateResStatus fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style UpdateCatalog fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style GenerateReceipt fill:#c5cae9,stroke:#3949ab,stroke-width:2px style LogTransaction fill:#bcaaa4,stroke:#5d4037,stroke-width:2px style InvalidItem fill:#ffcdd2,stroke:#c62828,stroke-width:2px style NotCheckedOut fill:#ffcdd2,stroke:#c62828,stroke-width:2px style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px
3. Reservation and Notification Flow
flowchart TD Start([๐ค Member Wants Book]) --> SearchCatalog[๐ Search Catalog<br/>for Desired Book] SearchCatalog --> BookFound{Book<br/>Found?} BookFound -->|No| SuggestAlternatives[๐ก Suggest Similar Books<br/>Alternative Titles] SuggestAlternatives --> End1([โ Book Not Available]) BookFound -->|Yes| CheckAvailable{Available<br/>Copies?} CheckAvailable -->|Yes| ProceedCheckout[โ Proceed to Checkout Flow] ProceedCheckout --> End2([๐ Go to Checkout]) CheckAvailable -->|No| CheckResLimit{Already<br/>Reserved Max Books?} CheckResLimit -->|Yes| DenyReservation[โ Maximum Reservations<br/>Limit Reached] DenyReservation --> End1 CheckResLimit -->|No| CreateRes[๐ Create Reservation Record<br/>Generate Reservation ID] CreateRes --> AddToQueue[๐ Add to Reservation Queue<br/>for This Book (FIFO)] AddToQueue --> CalcPosition[๐ข Calculate Queue Position] CalcPosition --> NotifyPosition[๐ง Notify Member of<br/>Queue Position & Est. Wait] NotifyPosition --> SetStatus[๐ Set Reservation Status<br/>to PENDING] SetStatus --> MonitorQueue[๐๏ธ System Monitors Queue] MonitorQueue --> End3([โณ Waiting in Queue]) MonitorQueue -.->|Book Returned| BookReturned[๐ Book Item Returned<br/>by Another Member] BookReturned --> ProcessQueue[๐ Reservation Manager<br/>Processes Queue] ProcessQueue --> GetNext[๐ Get Next Pending<br/>Reservation (FIFO)] GetNext --> UpdateItem[๐ Update BookItem State<br/>to RESERVED] UpdateItem --> UpdateResStatus[๐ Update Reservation Status<br/>to NOTIFIED] UpdateResStatus --> SetHoldExpiry[โฐ Set Hold Expiry<br/>48 Hours from Now] SetHoldExpiry --> SendMultiChannel[๐จ Send Multi-Channel Notification] SendMultiChannel --> SendEmail[๐ง Send Email Notification<br/>"Your Reserved Book is Ready!"] SendMultiChannel --> SendSMS[๐ฑ Send SMS Notification<br/>"Book #{barcode} ready at desk"] SendMultiChannel --> SendPush[๐ Send Push Notification<br/>to Mobile App] SendEmail --> WaitPickup[โฐ Wait for Member Pickup<br/>48 Hour Hold Period] SendSMS --> WaitPickup SendPush --> WaitPickup WaitPickup --> PickupCheck{Member<br/>Picked Up?} PickupCheck -->|Yes, Within 48h| ValidateRes[โ Validate Reservation<br/>at Checkout Desk] ValidateRes --> CheckoutReserved[๐ Checkout Reserved Book<br/>to Member] CheckoutReserved --> CompleteRes[โ Update Reservation Status<br/>to COMPLETED] CompleteRes --> End4([โ Reservation Fulfilled]) PickupCheck -->|No, After 48h| Send24hReminder{Sent 24h<br/>Reminder?} Send24hReminder -->|No| SendReminder[โ ๏ธ Send Expiry Reminder<br/>24 Hours Before Expiry] SendReminder --> WaitPickup Send24hReminder -->|Yes| HoldExpired[โฐ Hold Period Expired] HoldExpired --> CancelRes[โ Auto-Cancel Reservation<br/>Status: EXPIRED] CancelRes --> ReleaseItem[๐ Release BookItem<br/>Back to AVAILABLE] ReleaseItem --> NotifyCancel[๐ง Notify Member<br/>Reservation Expired] NotifyCancel --> CheckNextQueue{More Reservations<br/>in Queue?} CheckNextQueue -->|Yes| ProcessQueue CheckNextQueue -->|No| MakeAvailable[๐ Make Book Available<br/>for General Checkout] MakeAvailable --> End5([๐ Back to Catalog]) PickupCheck -.->|Member Cancels| MemberCancel[๐ค Member Cancels<br/>Reservation Manually] MemberCancel --> UpdateCancelStatus[๐ Update Status<br/>to CANCELLED] UpdateCancelStatus --> ReleaseItem style Start fill:#e3f2fd,stroke:#1565c0,stroke-width:3px style SearchCatalog fill:#b2dfdb,stroke:#00695c,stroke-width:2px style BookFound fill:#fff9c4,stroke:#f57f17,stroke-width:2px style CheckAvailable fill:#fff59d,stroke:#fbc02d,stroke-width:2px style CheckResLimit fill:#e1bee7,stroke:#8e24aa,stroke-width:2px style CreateRes fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style AddToQueue fill:#ffe0b2,stroke:#e65100,stroke-width:2px style CalcPosition fill:#bbdefb,stroke:#1976d2,stroke-width:2px style NotifyPosition fill:#b3e5fc,stroke:#0277bd,stroke-width:2px style SetStatus fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px style MonitorQueue fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style BookReturned fill:#dcedc8,stroke:#558b2f,stroke-width:2px style ProcessQueue fill:#ffccbc,stroke:#d84315,stroke-width:2px style GetNext fill:#ffe0b2,stroke:#e65100,stroke-width:2px style UpdateItem fill:#c5e1a5,stroke:#558b2f,stroke-width:2px style UpdateResStatus fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style SetHoldExpiry fill:#ffe082,stroke:#f9a825,stroke-width:2px style SendMultiChannel fill:#b2ebf2,stroke:#006064,stroke-width:2px style SendEmail fill:#90caf9,stroke:#1565c0,stroke-width:2px style SendSMS fill:#ce93d8,stroke:#6a1b9a,stroke-width:2px style SendPush fill:#80deea,stroke:#00838f,stroke-width:2px style WaitPickup fill:#d1c4e9,stroke:#5e35b1,stroke-width:2px style PickupCheck fill:#fff9c4,stroke:#f57f17,stroke-width:2px style ValidateRes fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style CheckoutReserved fill:#a5d6a7,stroke:#388e3c,stroke-width:2px style CompleteRes fill:#81c784,stroke:#2e7d32,stroke-width:2px style Send24hReminder fill:#fff59d,stroke:#fbc02d,stroke-width:2px style SendReminder fill:#ffe082,stroke:#f9a825,stroke-width:2px style HoldExpired fill:#ffab91,stroke:#d84315,stroke-width:2px style CancelRes fill:#ffcdd2,stroke:#c62828,stroke-width:2px style ReleaseItem fill:#b2dfdb,stroke:#00796b,stroke-width:2px style NotifyCancel fill:#f48fb1,stroke:#ad1457,stroke-width:2px style CheckNextQueue fill:#fff9c4,stroke:#f57f17,stroke-width:2px style MakeAvailable fill:#c8e6c9,stroke:#388e3c,stroke-width:2px style MemberCancel fill:#ffccbc,stroke:#d84315,stroke-width:2px style UpdateCancelStatus fill:#f8bbd0,stroke:#c2185b,stroke-width:2px style SuggestAlternatives fill:#ffe0b2,stroke:#e65100,stroke-width:2px style DenyReservation fill:#ffcdd2,stroke:#c62828,stroke-width:2px style ProceedCheckout fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px style End1 fill:#ef5350,stroke:#c62828,stroke-width:3px style End2 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style End3 fill:#ff8a65,stroke:#d84315,stroke-width:3px style End4 fill:#66bb6a,stroke:#2e7d32,stroke-width:3px style End5 fill:#42a5f5,stroke:#1565c0,stroke-width:3px
Implementation
Due to the extensive size of the complete Java and Python implementations (which would exceed token limits), I'll provide the key portions demonstrating the design patterns. The full implementations follow the same structure as previous case studies.
Java Implementation (Key Components)
package library;
import java.time.*;
import java.util.*;
import java.math.BigDecimal;
// =============== Enumerations ===============
enum BookFormat {
HARDCOVER, PAPERBACK, EBOOK, AUDIOBOOK, MAGAZINE, DVD
}
enum BookCondition {
NEW, GOOD, FAIR, WORN, DAMAGED
}
enum MembershipTier {
BASIC(5, 14, 1.0), // max books, lending days, fine rate
PREMIUM(10, 21, 0.5),
GOLD(15, 30, 0.25);
private final int maxBooks;
private final int lendingDays;
private final double fineRate;
MembershipTier(int maxBooks, int lendingDays, double fineRate) {
this.maxBooks = maxBooks;
this.lendingDays = lendingDays;
this.fineRate = fineRate;
}
public int getMaxBooks() { return maxBooks; }
public int getLendingDays() { return lendingDays; }
public double getFineRate() { return fineRate; }
}
enum AccountStatus {
ACTIVE, SUSPENDED, CLOSED, BLACKLISTED
}
enum LendingStatus {
ACTIVE, RETURNED, OVERDUE, LOST
}
enum ReservationStatus {
PENDING, NOTIFIED, COMPLETED, CANCELLED, EXPIRED
}
enum FineStatus {
PENDING, PAID, WAIVED
}
enum NotificationType {
DUE_DATE_REMINDER, OVERDUE_NOTICE, RESERVATION_AVAILABLE,
RESERVATION_EXPIRING, ACCOUNT_SUSPENDED
}
// =============== Value Objects ===============
class Money {
private final BigDecimal amount;
private final String currency;
public Money(double amount) {
this(BigDecimal.valueOf(amount), "USD");
}
public Money(BigDecimal amount, String currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Currency mismatch");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(double multiplier) {
return new Money(this.amount.multiply(BigDecimal.valueOf(multiplier)), this.currency);
}
public boolean greaterThan(Money other) {
return this.amount.compareTo(other.amount) > 0;
}
public BigDecimal getAmount() { return amount; }
@Override
public String toString() {
return currency + " " + amount.setScale(2, BigDecimal.ROUND_HALF_UP);
}
}
class Address {
private final String street;
private final String city;
private final String state;
private final String zipCode;
private final String country;
public Address(String street, String city, String state, String zipCode, String country) {
this.street = street;
this.city = city;
this.state = state;
this.zipCode = zipCode;
this.country = country;
}
public String getFullAddress() {
return street + ", " + city + ", " + state + " " + zipCode + ", " + country;
}
}
class Person {
private final String name;
private final String email;
private final String phone;
private final Address address;
public Person(String name, String email, String phone, Address address) {
this.name = name;
this.email = email;
this.phone = phone;
this.address = address;
}
public String getName() { return name; }
public String getEmail() { return email; }
public String getPhone() { return phone; }
}
// =============== State Pattern for BookItem ===============
interface BookItemState {
void checkout(BookItem item, Member member);
void returnItem(BookItem item);
void reserve(BookItem item, Member member);
}
class AvailableState implements BookItemState {
@Override
public void checkout(BookItem item, Member member) {
if (item.isReferenceOnly()) {
throw new IllegalStateException("Reference books cannot be checked out");
}
item.setState(new CheckedOutState());
}
@Override
public void returnItem(BookItem item) {
throw new IllegalStateException("Book is not checked out");
}
@Override
public void reserve(BookItem item, Member member) {
item.setState(new ReservedState());
}
}
class CheckedOutState implements BookItemState {
@Override
public void checkout(BookItem item, Member member) {
throw new IllegalStateException("Book is already checked out");
}
@Override
public void returnItem(BookItem item) {
item.setState(new AvailableState());
}
@Override
public void reserve(BookItem item, Member member) {
throw new IllegalStateException("Book is currently checked out");
}
}
class ReservedState implements BookItemState {
@Override
public void checkout(BookItem item, Member member) {
// Only the member who reserved can checkout
item.setState(new CheckedOutState());
}
@Override
public void returnItem(BookItem item) {
throw new IllegalStateException("Book is reserved, not checked out");
}
@Override
public void reserve(BookItem item, Member member) {
throw new IllegalStateException("Book is already reserved");
}
}
class LostState implements BookItemState {
@Override
public void checkout(BookItem item, Member member) {
throw new IllegalStateException("Book is marked as lost");
}
@Override
public void returnItem(BookItem item) {
// Found the lost book
item.setState(new AvailableState());
}
@Override
public void reserve(BookItem item, Member member) {
throw new IllegalStateException("Book is marked as lost");
}
}
// =============== Strategy Pattern for Fine Calculation ===============
interface FineCalculationStrategy {
Money calculateFine(int daysOverdue, MembershipTier tier);
}
class StandardFineStrategy implements FineCalculationStrategy {
private static final Money MAX_FINE_PER_BOOK = new Money(50.0);
@Override
public Money calculateFine(int daysOverdue, MembershipTier tier) {
if (daysOverdue <= 0) {
return new Money(0.0);
}
Money dailyRate = new Money(tier.getFineRate());
Money totalFine = dailyRate.multiply(daysOverdue);
// Apply maximum cap
if (totalFine.greaterThan(MAX_FINE_PER_BOOK)) {
return MAX_FINE_PER_BOOK;
}
return totalFine;
}
}
class GracePeriodFineStrategy implements FineCalculationStrategy {
private final FineCalculationStrategy baseStrategy;
private final Map<MembershipTier, Integer> gracePeriods;
public GracePeriodFineStrategy(FineCalculationStrategy baseStrategy) {
this.baseStrategy = baseStrategy;
this.gracePeriods = new HashMap<>();
gracePeriods.put(MembershipTier.BASIC, 1);
gracePeriods.put(MembershipTier.PREMIUM, 2);
gracePeriods.put(MembershipTier.GOLD, 3);
}
@Override
public Money calculateFine(int daysOverdue, MembershipTier tier) {
int graceDays = gracePeriods.get(tier);
int billableDays = Math.max(0, daysOverdue - graceDays);
return baseStrategy.calculateFine(billableDays, tier);
}
}
// =============== Observer Pattern for Notifications ===============
interface NotificationChannel {
void send(String recipient, String message);
}
class EmailNotification implements NotificationChannel {
private final String smtpServer;
public EmailNotification(String smtpServer) {
this.smtpServer = smtpServer;
}
@Override
public void send(String recipient, String message) {
System.out.println("๐ง Sending email to " + recipient + ": " + message);
// Simulate email sending
}
}
class SMSNotification implements NotificationChannel {
private final String twilioApiKey;
public SMSNotification(String twilioApiKey) {
this.twilioApiKey = twilioApiKey;
}
@Override
public void send(String recipient, String message) {
System.out.println("๐ฑ Sending SMS to " + recipient + ": " + message);
// Simulate SMS sending
}
}
class PushNotification implements NotificationChannel {
private final String firebaseKey;
public PushNotification(String firebaseKey) {
this.firebaseKey = firebaseKey;
}
@Override
public void send(String recipient, String message) {
System.out.println("๐ Sending push notification to " + recipient + ": " + message);
// Simulate push notification
}
}
class NotificationService {
private List<NotificationChannel> channels;
public NotificationService() {
this.channels = new ArrayList<>();
}
public void registerChannel(NotificationChannel channel) {
channels.add(channel);
}
public void notify(Member member, String message, NotificationType type) {
for (NotificationChannel channel : channels) {
channel.send(member.getPerson().getEmail(), message);
}
}
public void sendDueDateReminder(BookLending lending) {
String message = String.format(
"Reminder: Book '%s' is due on %s",
lending.getBookItem().getBook().getTitle(),
lending.getDueDate()
);
notify(lending.getMember(), message, NotificationType.DUE_DATE_REMINDER);
}
public void sendReservationAvailable(Reservation reservation) {
String message = String.format(
"Your reserved book '%s' is now available for pickup! Hold expires in 48 hours.",
reservation.getBook().getTitle()
);
notify(reservation.getMember(), message, NotificationType.RESERVATION_AVAILABLE);
}
}
// =============== Command Pattern for Library Operations ===============
interface LibraryCommand {
void execute();
void undo();
}
class CheckoutCommand implements LibraryCommand {
private final Member member;
private final BookItem bookItem;
private final Library library;
private BookLending lending;
public CheckoutCommand(Member member, BookItem bookItem, Library library) {
this.member = member;
this.bookItem = bookItem;
this.library = library;
}
@Override
public void execute() {
// Validate checkout
if (!member.canCheckout()) {
throw new IllegalStateException("Member cannot checkout more books");
}
// Calculate due date
LocalDateTime dueDate = LocalDateTime.now().plusDays(member.getTier().getLendingDays());
// Create lending record
lending = new BookLending(bookItem, member, dueDate);
// Update book item state
bookItem.setState(new CheckedOutState());
// Update member
member.addLending(lending);
System.out.println("โ
Checked out: " + bookItem.getBook().getTitle() +
" to " + member.getPerson().getName());
}
@Override
public void undo() {
if (lending != null) {
bookItem.setState(new AvailableState());
member.removeLending(lending);
}
}
}
// =============== Composite Pattern for Search ===============
interface SearchCriteria {
boolean matches(Book book);
}
class TitleSearchCriteria implements SearchCriteria {
private final String title;
public TitleSearchCriteria(String title) {
this.title = title.toLowerCase();
}
@Override
public boolean matches(Book book) {
return book.getTitle().toLowerCase().contains(title);
}
}
class AuthorSearchCriteria implements SearchCriteria {
private final String authorName;
public AuthorSearchCriteria(String authorName) {
this.authorName = authorName.toLowerCase();
}
@Override
public boolean matches(Book book) {
return book.getAuthors().stream()
.anyMatch(author -> author.getName().toLowerCase().contains(authorName));
}
}
enum SearchOperator {
AND, OR
}
class CompositeSearchCriteria implements SearchCriteria {
private final List<SearchCriteria> criteria;
private final SearchOperator operator;
public CompositeSearchCriteria(SearchOperator operator) {
this.criteria = new ArrayList<>();
this.operator = operator;
}
public void addCriteria(SearchCriteria criterion) {
criteria.add(criterion);
}
@Override
public boolean matches(Book book) {
if (criteria.isEmpty()) {
return true;
}
if (operator == SearchOperator.AND) {
return criteria.stream().allMatch(c -> c.matches(book));
} else {
return criteria.stream().anyMatch(c -> c.matches(book));
}
}
}
// =============== Core Domain Classes ===============
class Author {
private final String authorId;
private final String name;
private String biography;
private List<Book> books;
public Author(String name) {
this.authorId = UUID.randomUUID().toString();
this.name = name;
this.books = new ArrayList<>();
}
public String getName() { return name; }
public void addBook(Book book) { books.add(book); }
}
class Book {
private final String isbn;
private String title;
private List<Author> authors;
private String publisher;
private String subject;
private LocalDate publicationDate;
private int numberOfPages;
private List<String> tags;
private List<BookItem> items;
public Book(String isbn, String title, List<Author> authors) {
this.isbn = isbn;
this.title = title;
this.authors = authors;
this.items = new ArrayList<>();
this.tags = new ArrayList<>();
}
public void addBookItem(BookItem item) {
items.add(item);
}
public List<BookItem> getAvailableItems() {
return items.stream()
.filter(BookItem::isAvailable)
.collect(java.util.stream.Collectors.toList());
}
public int getTotalCopies() {
return items.size();
}
// Getters
public String getIsbn() { return isbn; }
public String getTitle() { return title; }
public List<Author> getAuthors() { return authors; }
public String getPublisher() { return publisher; }
public String getSubject() { return subject; }
public void setPublisher(String publisher) { this.publisher = publisher; }
public void setSubject(String subject) { this.subject = subject; }
}
class BookItem {
private final String barcode;
private final Book book;
private BookFormat format;
private BookItemState state;
private BookCondition condition;
private Money price;
private LocalDate purchasedDate;
private String rackLocation;
private boolean isReferenceOnly;
public BookItem(String barcode, Book book, BookFormat format, Money price) {
this.barcode = barcode;
this.book = book;
this.format = format;
this.price = price;
this.state = new AvailableState();
this.condition = BookCondition.NEW;
this.purchasedDate = LocalDate.now();
this.isReferenceOnly = false;
}
public boolean isAvailable() {
return state instanceof AvailableState;
}
public void checkout(Member member) {
state.checkout(this, member);
}
public void returnItem() {
state.returnItem(this);
}
public void reserve(Member member) {
state.reserve(this, member);
}
public void setState(BookItemState state) {
this.state = state;
}
// Getters
public String getBarcode() { return barcode; }
public Book getBook() { return book; }
public BookFormat getFormat() { return format; }
public boolean isReferenceOnly() { return isReferenceOnly; }
public void setReferenceOnly(boolean referenceOnly) { this.isReferenceOnly = referenceOnly; }
}
// Continue with remaining classes: Member, BookLending, Reservation, etc.
// (Following the same pattern as previous implementations)
Python Implementation (Key Components)
from enum import Enum
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Protocol
from datetime import datetime, timedelta, date
from abc import ABC, abstractmethod
from decimal import Decimal
import uuid
from collections import deque
# =============== Enumerations ===============
class BookFormat(Enum):
HARDCOVER = "hardcover"
PAPERBACK = "paperback"
EBOOK = "ebook"
AUDIOBOOK = "audiobook"
MAGAZINE = "magazine"
DVD = "dvd"
class BookCondition(Enum):
NEW = "new"
GOOD = "good"
FAIR = "fair"
WORN = "worn"
DAMAGED = "damaged"
class MembershipTier(Enum):
BASIC = ("basic", 5, 14, 1.0)
PREMIUM = ("premium", 10, 21, 0.5)
GOLD = ("gold", 15, 30, 0.25)
def __init__(self, tier_name: str, max_books: int, lending_days: int, fine_rate: float):
self.tier_name = tier_name
self.max_books = max_books
self.lending_days = lending_days
self.fine_rate = fine_rate
class AccountStatus(Enum):
ACTIVE = "active"
SUSPENDED = "suspended"
CLOSED = "closed"
BLACKLISTED = "blacklisted"
class LendingStatus(Enum):
ACTIVE = "active"
RETURNED = "returned"
OVERDUE = "overdue"
LOST = "lost"
class ReservationStatus(Enum):
PENDING = "pending"
NOTIFIED = "notified"
COMPLETED = "completed"
CANCELLED = "cancelled"
EXPIRED = "expired"
class FineStatus(Enum):
PENDING = "pending"
PAID = "paid"
WAIVED = "waived"
class NotificationType(Enum):
DUE_DATE_REMINDER = "due_date_reminder"
OVERDUE_NOTICE = "overdue_notice"
RESERVATION_AVAILABLE = "reservation_available"
RESERVATION_EXPIRING = "reservation_expiring"
ACCOUNT_SUSPENDED = "account_suspended"
# =============== Value Objects ===============
@dataclass(frozen=True)
class Money:
amount: Decimal
currency: str = "USD"
def __post_init__(self):
if self.amount < 0:
raise ValueError("Amount cannot be negative")
def add(self, other: 'Money') -> 'Money':
if self.currency != other.currency:
raise ValueError("Currency mismatch")
return Money(self.amount + other.amount, self.currency)
def multiply(self, multiplier: float) -> 'Money':
return Money(self.amount * Decimal(str(multiplier)), self.currency)
def greater_than(self, other: 'Money') -> bool:
return self.amount > other.amount
def __str__(self) -> str:
return f"{self.currency} {self.amount:.2f}"
@dataclass(frozen=True)
class Address:
street: str
city: str
state: str
zip_code: str
country: str
def get_full_address(self) -> str:
return f"{self.street}, {self.city}, {self.state} {self.zip_code}, {self.country}"
@dataclass
class Person:
name: str
email: str
phone: str
address: Address
# =============== State Pattern for BookItem ===============
class BookItemState(ABC):
@abstractmethod
def checkout(self, item: 'BookItem', member: 'Member'):
pass
@abstractmethod
def return_item(self, item: 'BookItem'):
pass
@abstractmethod
def reserve(self, item: 'BookItem', member: 'Member'):
pass
class AvailableState(BookItemState):
def checkout(self, item: 'BookItem', member: 'Member'):
if item.is_reference_only:
raise ValueError("Reference books cannot be checked out")
item.state = CheckedOutState()
def return_item(self, item: 'BookItem'):
raise ValueError("Book is not checked out")
def reserve(self, item: 'BookItem', member: 'Member'):
item.state = ReservedState()
class CheckedOutState(BookItemState):
def checkout(self, item: 'BookItem', member: 'Member'):
raise ValueError("Book is already checked out")
def return_item(self, item: 'BookItem'):
item.state = AvailableState()
def reserve(self, item: 'BookItem', member: 'Member'):
raise ValueError("Book is currently checked out")
class ReservedState(BookItemState):
def checkout(self, item: 'BookItem', member: 'Member'):
# Only the member who reserved can checkout
item.state = CheckedOutState()
def return_item(self, item: 'BookItem'):
raise ValueError("Book is reserved, not checked out")
def reserve(self, item: 'BookItem', member: 'Member'):
raise ValueError("Book is already reserved")
class LostState(BookItemState):
def checkout(self, item: 'BookItem', member: 'Member'):
raise ValueError("Book is marked as lost")
def return_item(self, item: 'BookItem'):
# Found the lost book
item.state = AvailableState()
def reserve(self, item: 'BookItem', member: 'Member'):
raise ValueError("Book is marked as lost")
# =============== Strategy Pattern for Fine Calculation ===============
class FineCalculationStrategy(Protocol):
def calculate_fine(self, days_overdue: int, tier: MembershipTier) -> Money:
...
class StandardFineStrategy:
MAX_FINE_PER_BOOK = Money(Decimal("50.0"))
def calculate_fine(self, days_overdue: int, tier: MembershipTier) -> Money:
if days_overdue <= 0:
return Money(Decimal("0.0"))
daily_rate = Money(Decimal(str(tier.fine_rate)))
total_fine = daily_rate.multiply(days_overdue)
# Apply maximum cap
if total_fine.greater_than(self.MAX_FINE_PER_BOOK):
return self.MAX_FINE_PER_BOOK
return total_fine
class GracePeriodFineStrategy:
def __init__(self, base_strategy: FineCalculationStrategy):
self.base_strategy = base_strategy
self.grace_periods = {
MembershipTier.BASIC: 1,
MembershipTier.PREMIUM: 2,
MembershipTier.GOLD: 3,
}
def calculate_fine(self, days_overdue: int, tier: MembershipTier) -> Money:
grace_days = self.grace_periods[tier]
billable_days = max(0, days_overdue - grace_days)
return self.base_strategy.calculate_fine(billable_days, tier)
# =============== Observer Pattern for Notifications ===============
class NotificationChannel(Protocol):
def send(self, recipient: str, message: str):
...
class EmailNotification:
def __init__(self, smtp_server: str):
self.smtp_server = smtp_server
def send(self, recipient: str, message: str):
print(f"๐ง Sending email to {recipient}: {message}")
class SMSNotification:
def __init__(self, twilio_api_key: str):
self.twilio_api_key = twilio_api_key
def send(self, recipient: str, message: str):
print(f"๐ฑ Sending SMS to {recipient}: {message}")
class PushNotification:
def __init__(self, firebase_key: str):
self.firebase_key = firebase_key
def send(self, recipient: str, message: str):
print(f"๐ Sending push notification to {recipient}: {message}")
class NotificationService:
def __init__(self):
self.channels: List[NotificationChannel] = []
def register_channel(self, channel: NotificationChannel):
self.channels.append(channel)
def notify(self, member: 'Member', message: str, notification_type: NotificationType):
for channel in self.channels:
channel.send(member.person.email, message)
def send_due_date_reminder(self, lending: 'BookLending'):
message = f"Reminder: Book '{lending.book_item.book.title}' is due on {lending.due_date}"
self.notify(lending.member, message, NotificationType.DUE_DATE_REMINDER)
def send_reservation_available(self, reservation: 'Reservation'):
message = f"Your reserved book '{reservation.book.title}' is now available for pickup! Hold expires in 48 hours."
self.notify(reservation.member, message, NotificationType.RESERVATION_AVAILABLE)
# =============== Command Pattern for Library Operations ===============
class LibraryCommand(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class CheckoutCommand(LibraryCommand):
def __init__(self, member: 'Member', book_item: 'BookItem', library: 'Library'):
self.member = member
self.book_item = book_item
self.library = library
self.lending: Optional['BookLending'] = None
def execute(self):
# Validate checkout
if not self.member.can_checkout():
raise ValueError("Member cannot checkout more books")
# Calculate due date
due_date = datetime.now() + timedelta(days=self.member.tier.lending_days)
# Create lending record
self.lending = BookLending(self.book_item, self.member, due_date)
# Update book item state
self.book_item.state = CheckedOutState()
# Update member
self.member.add_lending(self.lending)
print(f"โ
Checked out: {self.book_item.book.title} to {self.member.person.name}")
def undo(self):
if self.lending:
self.book_item.state = AvailableState()
self.member.remove_lending(self.lending)
# =============== Composite Pattern for Search ===============
class SearchCriteria(ABC):
@abstractmethod
def matches(self, book: 'Book') -> bool:
pass
class TitleSearchCriteria(SearchCriteria):
def __init__(self, title: str):
self.title = title.lower()
def matches(self, book: 'Book') -> bool:
return self.title in book.title.lower()
class AuthorSearchCriteria(SearchCriteria):
def __init__(self, author_name: str):
self.author_name = author_name.lower()
def matches(self, book: 'Book') -> bool:
return any(self.author_name in author.name.lower() for author in book.authors)
class SearchOperator(Enum):
AND = "and"
OR = "or"
class CompositeSearchCriteria(SearchCriteria):
def __init__(self, operator: SearchOperator):
self.criteria: List[SearchCriteria] = []
self.operator = operator
def add_criteria(self, criterion: SearchCriteria):
self.criteria.append(criterion)
def matches(self, book: 'Book') -> bool:
if not self.criteria:
return True
if self.operator == SearchOperator.AND:
return all(c.matches(book) for c in self.criteria)
else:
return any(c.matches(book) for c in self.criteria)
# =============== Core Domain Classes ===============
@dataclass
class Author:
name: str
author_id: str = field(default_factory=lambda: str(uuid.uuid4()))
biography: str = ""
books: List['Book'] = field(default_factory=list)
def add_book(self, book: 'Book'):
self.books.append(book)
class Book:
def __init__(self, isbn: str, title: str, authors: List[Author]):
self.isbn = isbn
self.title = title
self.authors = authors
self.publisher: Optional[str] = None
self.subject: Optional[str] = None
self.publication_date: Optional[date] = None
self.number_of_pages: int = 0
self.tags: List[str] = []
self.items: List['BookItem'] = []
def add_book_item(self, item: 'BookItem'):
self.items.append(item)
def get_available_items(self) -> List['BookItem']:
return [item for item in self.items if item.is_available()]
def get_total_copies(self) -> int:
return len(self.items)
class BookItem:
def __init__(self, barcode: str, book: Book, format: BookFormat, price: Money):
self.barcode = barcode
self.book = book
self.format = format
self.price = price
self.state: BookItemState = AvailableState()
self.condition = BookCondition.NEW
self.purchased_date = date.today()
self.rack_location: Optional[str] = None
self.is_reference_only = False
def is_available(self) -> bool:
return isinstance(self.state, AvailableState)
def checkout(self, member: 'Member'):
self.state.checkout(self, member)
def return_item(self):
self.state.return_item(self)
def reserve(self, member: 'Member'):
self.state.reserve(self, member)
# Continue with remaining classes...
Key Design Decisions
-
State Pattern for Book Items: Manages complex state transitions (Available โ CheckedOut โ Returned, or Available โ Reserved โ CheckedOut) with proper validation at each step.
-
Strategy Pattern for Fine Calculation: Allows flexible fine policies including grace periods, membership-specific rates, and maximum caps without modifying core lending logic.
-
Observer Pattern for Notifications: Multi-channel notification system decouples notification delivery from business logic, enabling easy addition of new channels.
-
Command Pattern for Operations: Enables undo functionality, transaction logging, and batch operations for library activities.
-
Composite Pattern for Search: Supports complex search queries combining multiple criteria with AND/OR logic for powerful catalog searching.
-
Queue Management for Reservations: FIFO queue ensures fair access to reserved books with automatic notifications and expiry handling.