Determine the different ways the system will be used. This includes main functions the system needs to perform and who will use it.
titleauthorISBNstatus (available, checked out, etc.)dueDate (if checked out)userIDnameemailfines (total fines incurred)searchBooks()checkoutBook()returnBook()payFine()librarianIDnameemailaddBook()removeBook()manageUser()generateReport()transactionIDuserIDbookIDtransactionDatetransactionType (check out or return)bookCollectionuserListregisterUser()getAvailableBooks()findUser()Determine how these objects will interact with each other to fulfill the use cases...
Design inheritance trees where applicable to promote code reuse and polymorphism. This step involves identifying common attributes and behaviors that can be abstracted into parent classes...
Library class representing the entire library (managing books, users, and transactions).Member, Librarian) or Book based on certain criteria.User class with additional features) to existing objects.Attributes: For each class, define the attributes (data) it will hold...
Methods: Define the methods (functions) that operate on the attributes. Ensure they align with the object's responsibilities and adhere to the principle of encapsulation.
import abc # Abstract Base Classes
from datetime import datetime, timedelta
--- Observer Pattern ---
class Observer(abc.ABC):
"""Abstract Observer class."""
@abc.abstractmethod
def update(self, subject, message):
pass
class Subject(abc.ABC):
"""Abstract Subject class."""
def init(self):
self._observers = []
def attach(self, observer: Observer):
if observer not in self._observers:
self._observers.append(observer)
def detach(self, observer: Observer):
try:
self._observers.remove(observer)
except ValueError:
pass # Observer wasn't attached
def notify(self, message=None):
for observer in self._observers:
observer.update(self, message)
--- Composite Pattern ---
class LibraryItem(abc.ABC):
"""Component Interface for Composite Pattern."""
@abc.abstractmethod
def display_info(self, indent=""):
pass
def add(self, item):
# Default: cannot add to leaf nodes
raise NotImplementedError("Cannot add to this item")
def remove(self, item):
# Default: cannot remove from leaf nodes
raise NotImplementedError("Cannot remove from this item")
def get_books(self):
# Default: leaf node returns itself if it's a book
return []
--- Book Class (Leaf in Composite, Subject in Observer) ---
class Book(LibraryItem, Subject):
"""Represents a single book (Leaf in Composite, Subject in Observer)."""
def init(self, title, author, isbn):
LibraryItem.init(self)
Subject.init(self) # Initialize Subject part for Observer
self.title = title
self.author = author
self.isbn = isbn
self._status = "available"
self.due_date = None
self.borrower = None # Keep track of who borrowed it
def get_title(self):
return self.title
def get_author(self):
return self.author
def get_isbn(self):
return self.isbn
def get_status(self):
return self._status
def set_status(self, status, user=None, due_date=None):
"""Sets status and notifies observers."""
old_status = self._status
self._status = status
if status == "checked out":
self.borrower = user
self.due_date = due_date
# Attach borrower as observer when checked out
if user and isinstance(user, Observer):
self.attach(user)
self.notify(f"Book '{self.title}' is now checked out by {user.get_name() if user else 'Unknown'}.")
elif status == "available":
borrower_temp = self.borrower # Keep ref before clearing
self.due_date = None
self.borrower = None
# Detach former borrower when returned
if borrower_temp and isinstance(borrower_temp, Observer):
self.detach(borrower_temp)
self.notify(f"Book '{self.title}' is now available.")
else:
# Notify for other status changes if needed
self.notify(f"Book '{self.title}' status changed to {status}.")
def display_info(self, indent=""):
print(f"{indent}- Title: {self.title}, Author: {self.author}, ISBN: {self.isbn}, Status: {self._status}")
def get_books(self):
# A book is a book
return [self]
--- Book Collection (Composite in Composite) ---
class BookCollection(LibraryItem):
"""Represents a collection of LibraryItems (Composite in Composite)."""
def init(self, name):
self._children = []
self.name = name
def add(self, item: LibraryItem):
self._children.append(item)
def remove(self, item: LibraryItem):
self._children.remove(item)
def display_info(self, indent=""):
print(f"{indent}+ Collection: {self.name}")
for child in self._children:
child.display_info(indent + " ")
def get_books(self):
"""Recursively get all books within this collection."""
all_books = []
for child in self._children:
all_books.extend(child.get_books())
return all_books
--- User Class (Observer) ---
class User(Observer):
"""Represents a library user (Observer)."""
def init(self, user_id, name, email):
self.user_id = user_id
self.name = name
self.email = email
self.fines = 0.0
self.checked_out_books = [] # Keep track of books user has
def get_user_id(self):
return self.user_id
def get_name(self):
return self.name
def get_email(self):
return self.email
def get_fines(self):
return self.fines
def _actual_checkout_book(self, book: Book):
"""Internal logic for checking out a book."""
if book.get_status() == "available":
due = datetime.now().date() + timedelta(days=14)
book.set_status("checked out", user=self, due_date=due)
self.checked_out_books.append(book)
print(f"{self.name} checked out '{book.get_title()}'. Due: {due}")
return True
else:
print(f"Book '{book.get_title()}' is not available.")
return False
def _actual_return_book(self, book: Book):
"""Internal logic for returning a book."""
if book in self.checked_out_books:
book.set_status("available")
self.checked_out_books.remove(book)
print(f"{self.name} returned '{book.get_title()}'.")
# Calculate fines if overdue (basic example)
# if book.due_date and datetime.now().date() > book.due_date:
# overdue_days = (datetime.now().date() - book.due_date).days
# self.fines += overdue_days * 0.50 # Example fine
# print(f"Fine added: ${overdue_days * 0.50:.2f}")
return True
else:
print(f"{self.name} did not check out '{book.get_title()}' or it was already returned.")
return False
def pay_fine(self, amount):
self.fines -= amount
if self.fines < 0:
self.fines = 0
print(f"{self.name} paid ${amount:.2f}. Remaining fines: ${self.fines:.2f}")
def update(self, subject, message):
"""Receives notification from observed subjects (e.g., Books)."""
if isinstance(subject, Book):
print(f"Notification for {self.name}: {message}")
# Can add more specific logic based on the subject or message
--- Librarian Class (Subclass of User) ---
class Librarian(User):
"""Represents a librarian, inheriting User capabilities."""
def init(self, librarian_id, name, email):
# Librarians don't necessarily check out books or have fines in the same way
# Using User constructor might not be ideal, let's adjust
# super().init(librarian_id, name, email)
# Instead:
self.user_id = librarian_id # Use librarian_id as user_id for consistency
self.name = name
self.email = email
# Librarians don't typically have fines or checked out books as regular users
self.fines = 0.0
self.checked_out_books = [] # Maybe for tracking books they are handling?
def _actual_add_item(self, library, item: LibraryItem, collection: BookCollection = None):
"""Internal logic to add a book or collection."""
target_collection = collection if collection else library.get_catalog()
target_collection.add(item)
print(f"Librarian {self.name} added '{item.name if isinstance(item, BookCollection) else item.title}' to {'collection ' + collection.name if collection else 'main catalog'}.")
# Potentially notify users about new additions (another Observer use case?)
def _actual_remove_item(self, library, item: LibraryItem, collection: BookCollection = None):
"""Internal logic to remove a book or collection."""
target_collection = collection if collection else library.get_catalog()
try:
target_collection.remove(item)
print(f"Librarian {self.name} removed '{item.name if isinstance(item, BookCollection) else item.title}' from {'collection ' + collection.name if collection else 'main catalog'}.")
except ValueError:
print(f"Item not found in the specified location.")
def _actual_manage_user(self, library, user: User, action: str):
"""Internal logic to manage users."""
if action == 'add':
if user not in library.user_list:
library.user_list.append(user)
print(f"Librarian {self.name} registered user {user.get_name()}.")
else:
print(f"User {user.get_name()} is already registered.")
elif action == 'remove':
if user in library.user_list:
library.user_list.remove(user)
print(f"Librarian {self.name} removed user {user.get_name()}.")
else:
print(f"User {user.get_name()} not found.")
else:
print("Invalid user management action.")
def generate_report(self, library):
# Implement report generation logic using library data
print("\n--- Library Report ---")
print(f"Total Users Registered: {len(library.user_list)}")
all_books = library.get_all_books()
print(f"Total Books in Catalog: {len(all_books)}")
available_books = [b for b in all_books if b.get_status() == 'available']
checked_out_books = [b for b in all_books if b.get_status() == 'checked out']
print(f" Available Books: {len(available_books)}")
print(f" Checked Out Books: {len(checked_out_books)}")
# Add more details (overdue books, fines collected, etc.)
print("--- End Report ---\n")
Check and explain whether your design adheres to solid principles (Ask interviewer what SOLID principle is if you can not recall it.)...
Explain how your design can handle changes in scale and whether it would be easily to extend with new functionalities...
Try creating a class, flow, state and/or sequence diagram using the diagramming tool. Mermaid flow diagrams can be used to represent system use cases. You can ask the interviewer bot to create a starter diagram if unfamiliar with the tool. Briefly explain your diagrams if necessary...
Critically examine your design for any flaws or areas for future improvement...