Building a Library System to Apply Software Engineer Thinking

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]

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.