From procedural → class-based → design-pattern-level architecture, developing a library system serves as a profound method to implement software engineering principles.
Jumping to stage 2 that is class-based as the following, note the three classes are too tightly coupled, not an ideal practice:class Book:
def __init__(self, title, author):
self.title = title
self.author = author
self.is_borrowed = False
def borrow(self):
if not self.is_borrowed:
self.is_borrowed = True
return True
return False
def return_book(self):
self.is_borrowed = False
class User:
def __init__(self, name):
self.name = name
self.borrowed_books = []
def borrow_book(self, book):
if book.borrow():
self.borrowed_books.append(book)
print(f”{self.name} borrowed {book.title}”)
else:
print(f”{book.title} is already borrowed.”)
class Library:
def __init__(self):
self.books = []
def add_book(self, book):
self.books.append(book)
def find_book(self, title):
for b in self.books:
if b.title == title:
return b
return None
Stage 3 — Applying Design Patterns (Encapsulation + Communication): library mediator, observer to notify users, and memento system to track, undo and versioning books.
class LibraryMediator:
def __init__(self):
self.books = []
self.users = []
def register_user(self, user):
self.users.append(user)
user.mediator = self
def register_book(self, book):
self.books.append(book)
def borrow_request(self, user, title):
for b in self.books:
if b.title == title and not b.is_borrowed:
b.is_borrowed = True
user.borrowed_books.append(b)
print(f"{user.name} borrowed '{b.title}'")
return
print(f"'{title}' is not available.")
class User:
def __init__(self, name):
self.name = name
self.borrowed_books = []
self.mediator = None
def borrow(self, title):
if self.mediator:
self.mediator.borrow_request(self, title)
# now Modify the class Book to add observer pattern i.e. notification system for users who subscribe or waitlist for a book
class Book:
def __init__(self, title):
self.title = title
self.is_borrowed = False
self.subscribers = [] # observers
def subscribe(self, user):
self.subscribers.append(user)
def borrow(self, user):
if not self.is_borrowed:
self.is_borrowed = True
print(f"{user.name} borrowed '{self.title}'")
else:
print(f"'{self.title}' is already borrowed")
def return_book(self):
self.is_borrowed = False
for s in self.subscribers:
print(f"Notify {s.name}: '{self.title}' is now available")
self.subscribers.clear()
# Add memento to track the state of library
class LibraryMemento:
def __init__(self, books_snapshot):
self.books_snapshot = [b.title for b in books_snapshot]
class Library:
def __init__(self):
self.books = []
self.history = []
def add_book(self, book):
self.save_state()
self.books.append(book)
def save_state(self):
self.history.append(LibraryMemento(list(self.books)))
def undo(self):
if self.history:
memento = self.history.pop()
self.books = [Book(t) for t in memento.books_snapshot]