DEV Community

Vafa
Vafa

Posted on • Edited on

OOP and SOLID in python

The Scenario: A Library's Diverse Collection

Picture a bustling public library where patrons borrow everything from page-turners to quick-watch flicks. To keep things simple yet realistic, we'll focus on three core types of items:

Book: The classic staple, complete with an author (the storyteller), title (the allure), and pages (the depth of the dive). Borrowing a book should feel like checking out a companion for your next rainy afternoon.
Magazine: Lightweight and current, featuring a title (like "Tech Trends" or "Garden Gazette") and issue number (that fresh-off-the-press vibe). Ideal for a short commute read—nothing too committing.
DVD: For those who prefer stories on screen, with a title (the plot hook) and duration in minutes (how long you'll be glued to the couch). Think family movie nights or indie film marathons.

The twist? Borrow fees aren't one-size-fits-all, reflecting the library's quirky policy to balance accessibility and revenue:

Books: 1 unit per 100 pages—encouraging thoughtful borrowing of hefty reads.
Magazines: A flat 2 units—keep the stacks turning over quickly.
DVDs: 0.05 units per minute—fair for varying runtimes, from shorts to sagas.

This setup screams for OOP: we need polymorphism for those varying fee calculations, inheritance for shared traits (like a title), and encapsulation to bundle each item's logic neatly. But to avoid a tangled mess, SOLID principles guide the way—ensuring our code is robust without becoming rigid.
My Implementation: Building with Abstraction and Polymorphism
I started with an abstract base class (LibraryItem) to define the contract: every item must implement a borrow method that returns its fee. This enforces consistency while letting subclasses shine with their unique logic. Here's the code I came up with:


from abc import ABC, abstractmethod

class LibraryItem(ABC):
    def __init__(self, title: str):
        super().__init__()
        self.title = title

    @abstractmethod
    def borrow(self):
        pass

class Book(LibraryItem):
    def __init__(self, title: str, author: str, pages: int):
        super().__init__(title)
        self.author = author
        self.pages = pages

    def borrow(self):
        return 1 * (self.pages / 100)

class Magazine(LibraryItem):
    def __init__(self, title: str, issue_number: int):
        super().__init__(title)
        self.issue_number = issue_number

    def borrow(self):
        return 2

class DVD(LibraryItem):
    def __init__(self, title: str, duration: int):
        super().__init__(title)
        self.duration = duration

    def borrow(self):
        return self.duration * 0.05


class Library:
    def __init__(self):
        self.tracked = []

    def borrow_request(self, library_item: LibraryItem):
        self.tracked.append(library_item)

    def total_fee(self):
        total_fee = 0.0
        for item in self.tracked:
            total_fee += item.borrow()

        return {"borrowed_items": [track_item.title for track_item in self.tracked], "total_fee": total_fee}
Enter fullscreen mode Exit fullscreen mode

Why This Design? Breaking Down the OOP and SOLID Choices

I leaned on abstract methods because the borrow function is essential for all child classes of LibraryItem, but its implementation varies wildly—polymorphism at its finest. When the Library class iterates over items and calls borrow(), it doesn't care about the type; it just gets the right fee. This is pure magic, making the code flexible and intuitive.
Zooming out, this embodies several SOLID principles:

Single Responsibility Principle (SRP): Each class has one job. LibraryItem handles the abstract contract, subclasses focus solely on their attributes and fee logic, and Library manages the collection without meddling in calculations.
Open-Closed Principle (OCP): The system is open for extension (add a Comic class? Easy—just inherit and override borrow) but closed for modification (no need to tweak Library or the base class for new types).
Liskov Substitution Principle (LSP): Any LibraryItem subtype can stand in for the parent without breaking things—total_fee works seamlessly with books, magazines, or DVDs.
Interface Segregation Principle (ISP): We're not forcing every item to implement unrelated methods; the abstract interface is slim, just what's needed.
Dependency Inversion Principle (DIP): High-level Library depends on the abstract LibraryItem, not concrete classes, keeping coupling low.

In short, my sample code supports polymorphism beautifully and is highly extendable. Want to add audiobooks with fees based on listening hours? Subclass away—no ripples in the existing pond.

If you have any questions, suggestions, or ideas for improving this implementation (or extending it further), I'd love to hear them—feel free to drop a comment below!

Top comments (0)