OBJECT-ORIENTED PROGRAMMING β€” ADVANCED PATTERNS & PRACTICES

Glenn Junsay Pansensoy

πŸ“š LIBRARY MANAGEMENT β€” encapsulation Β· MRO Β· mixins Β· slots

Complete library system with books, members, loans, fines, and holds. Demonstrates private attributes via name mangling, property decorators for controlled access, method resolution order with multiple inheritance, and mixins for timestamp/logging. Includes specialised collections: reference items (in‑library use), periodicals (different loan periods), digital media (DRM flags). Fine calculation uses strategy pattern per item type. Holds queue with priority for researchers.

πŸ” ENCAPSULATION DEEP DIVE: The double underscore __attr triggers name mangling to _ClassName__attr, preventing accidental overrides. Properties act as computed gatesβ€”notice how @property lets us expose _title read‑only while keeping write access internal. This library uses __slots__ in ReferenceBook to reduce memory footprint by ~40% for thousands of reference items. The mixin (TimestampMixin) adds creation time without polluting the main hierarchy.
class TimestampMixin:
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.created_at = __import__('datetime').datetime.now()

class LibraryItem:
    def __init__(self, title, isbn):
        self._title = title
        self._isbn = isbn
        self.checked_out = False
        self._hold_queue = []

    @property
    def title(self): return self._title

    @property
    def isbn(self): return self._isbn

class Book(LibraryItem):
    def __init__(self, title, isbn, author, pages):
        super().__init__(title, isbn)
        self.author = author
        self.pages = pages
        self.genre = 'unknown'

class ReferenceBook(Book, TimestampMixin):
    __slots__ = ('in_library_use', 'citation')
    def __init__(self, title, isbn, author, pages):
        super().__init__(title, isbn, author, pages)
        self.in_library_use = True
        self.citation = f"{author}, '{title}' ({isbn})"
        
@property __slots__ mixin MRO name mangling hold queue fine strategy
[ready] β€” click to see encapsulation, mixins, MRO in action

πŸ’° BANKING SYSTEM β€” abstract base Β· composition Β· decorator pattern

Multi‑account system with transaction log, overdraft protection, interest strategies. Uses ABC to enforce withdraw/deposit contracts. Supports checking, savings, money market. Composition through TransactionHistory objects and customer profiles. Implements decorator pattern for overdraft and interest β€” can stack features. Shows polymorphism via process_monthly method that behaves differently per account.

🏦 WHY ABC? The Account abstract base class forces all subclasses to implement apply_monthly. Without it, a subclass could omit the method and cause runtime errors. Composition shines here: each account has a history list (instead of inheriting from a History class). The decorator pattern wraps an account with overdraft capabilities without modifying the original classβ€”this respects the Open/Closed principle.
from abc import ABC, abstractmethod

class Account(ABC):
    def __init__(self, owner):
        self.owner = owner
        self._balance = 0.0
        self.history = []

    @abstractmethod
    def apply_monthly(self):
        pass

class Savings(Account):
    def __init__(self, owner, rate=0.02):
        super().__init__(owner)
        self.rate = rate

    def apply_monthly(self):
        interest = self._balance * self.rate
        self._balance += interest
        self.history.append(f"interest +{interest:.2f}")

class OverdraftDecorator:
    def __init__(self, account, limit=200):
        self._account = account
        self.limit = limit

    def withdraw(self, amount):
        if amount <= self._account._balance + self.limit:
            self._account._balance -= amount
            return True
        return False
        
ABC abstractmethod composition decorator pattern overdraft interest
[ready] β€” click to see polymorphism & decorator stacking

πŸš— RIDE SHARING β€” aggregation Β· DI Β· observer location

Driver, rider, trip with loose coupling. Trips aggregate references (no ownership). Dependency injection for fare calculator β€” can swap surge/regular. Bidirectional association: driver knows trips, rider knows trips. Implements observer for real‑time driver location β€” passengers get push updates. Surge pricing based on demand factor, vehicle type (premium/XL). Strategy for cancellation fees.

πŸ’‰ DEPENDENCY INJECTION IN ACTION: The Trip class receives its fare calculator externally (default or custom). This allows swapping algorithms (surge, flat, distance‑based) without changing Trip. Aggregation (vs composition) means that if a trip ends, the driver and rider objects continue to existβ€”they are independent. The observer pattern for location updates would let multiple riders subscribe to a driver's position; we simulate this in the demo with stock price observers.
class FareCalculator:
    def calculate(self, distance, vehicle_type, demand=1.0):
        base = distance * 1.2
        if vehicle_type == 'premium':
            base *= 1.8
        return base * demand

class Trip:
    def __init__(self, rider, driver, distance, fare_calc=None):
        self.rider = rider
        self.driver = driver
        self.distance = distance
        self._calc = fare_calc or FareCalculator()
        self.status = 'ongoing'

    @property
    def fare(self):
        return self._calc.calculate(self.distance, self.driver.vehicle_type)

class Driver:
    def __init__(self, name, vehicle_type):
        self.name = name
        self.vehicle_type = vehicle_type
        self.trips = []
        self.location = (0,0)
        
dependency injection aggregation observer strategy(fare) classmethod
[ready] β€” click to see DI, aggregation, surge logic

πŸ›’ STRATEGY PATTERN β€” open/closed Β· DI Β· composable discounts

Shopping cart with multiple discount strategies: percentage, fixed amount, BOGO, loyalty, seasonal. Strategies are interchangeable and can be stacked (composite strategy). Uses dependency injection at checkout. Open/closed principle: new discount types don't modify cart. Includes threshold rules (min purchase) and coupon codes. Real‑time subtotal calculation with strategy chaining.

πŸ”„ OPEN/CLOSED IN PRACTICE: The DiscountStrategy hierarchy lets us add new discount types (like "loyalty 15%") without ever touching the Cart class. The CompositeStrategy demonstrates how multiple strategies can be combinedβ€”this is a variation of the Composite pattern. The demo shows 10% off stacked with BOGO, which is order‑sensitive; in real systems you'd define precedence rules.
class DiscountStrategy:
    def apply(self, cart):
        raise NotImplementedError

class PercentageOff(DiscountStrategy):
    def __init__(self, percent): self.percent = percent
    def apply(self, cart): return cart.subtotal * (1 - self.percent/100)

class BOGO(DiscountStrategy):
    def apply(self, cart):
        items = cart.items
        discount = 0
        for i in items:
            if i.qty >= 2:
                discount += i.price * (i.qty // 2)
        return cart.subtotal - discount

class CompositeStrategy:
    def __init__(self, *strategies): self.strats = strategies
    def apply(self, cart):
        total = cart.subtotal
        for s in self.strats:
            total = s.apply(cart.__class__(items=cart.items, subtotal=total))
        return total
        
strategy pattern open/closed composite DI BOGO
[ready] β€” click to see percentage + BOGO combined

πŸ“Š OBSERVER PATTERN β€” pub/sub Β· loose coupling Β· event‑driven

Stock price notifier with multiple subscribers: logger, alert (threshold), analytics (moving average), dashboard. Both push and pull models. Attach/detach at runtime. Demonstrates one‑to‑many dependency without coupling subjects to concrete observers. Also used in ride sharing location updates. Implements custom event types: price change, volume spike, news alert.

πŸ”— LOOSE COUPLING: The Subject knows only that observers have an update methodβ€”no details about their internals. This lets us add new observers (like a Slack notifier) without modifying the stock class. In our demo, the logger and alert observer react independently to price changes. This pattern is the foundation of event‑driven architectures and GUI frameworks.
class Subject:
    def __init__(self):
        self._observers = []

    def attach(self, observer): self._observers.append(observer)
    def detach(self, observer): self._observers.remove(observer)

    def notify(self, *args, **kwargs):
        for obs in self._observers:
            obs.update(self, *args, **kwargs)

class Observer:
    def update(self, subject, *args, **kwargs): pass

class AlertObserver(Observer):
    def __init__(self, threshold): self.threshold = threshold
    def update(self, subject, *args, **kwargs):
        price = subject.price
        if price > self.threshold:
            print(f"πŸ”” ALERT: ${price} exceeds {self.threshold}")
        
observer pub/sub event‑driven loose coupling push/pull
[ready] β€” click to see price alerts & logging

🏭 FACTORY PATTERN β€” abstract factory Β· SRP Β· creational

Document factory creating PDF, Word, HTML, Markdown, JSON, XML. Each document type has its own renderer and exporter (abstract factory). Adding new type requires zero changes to client code β€” just register new factory. Demonstrates factory method and abstract factory together. Also includes serialisation strategies for each format. Real‑world use: report generator with multiple output formats.

πŸ“‹ REGISTRY + FACTORY: The DocumentFactory._factories dictionary acts as a registryβ€”new document types are "plugged in" via register(). This is more flexible than a big if/elif chain; it follows the Open/Closed principle. The factory also centralizes object creation (Single Responsibility). In the demo, we register PDF, HTML, and Markdown builders and create documents without ever calling their constructors directly.
class Document:
    def render(self): pass
    def export(self): pass

class PDFDocument(Document):
    def __init__(self, title): self.title = title
    def render(self): return f"[PDF] {self.title}.pdf"
    def export(self): return f"%PDF-1.4 ... {self.title}"

class HTMLDocument(Document):
    def render(self): return f"

{self.title}

" class DocumentFactory: _factories = {} @classmethod def register(cls, ext, builder): cls._factories[ext] = builder @classmethod def create(cls, ext, title): return cls._factories[ext](title) DocumentFactory.register('pdf', PDFDocument) DocumentFactory.register('html', HTMLDocument)
factory method abstract factory SRP registry creational
[ready] β€” click to see factory with multiple formats

🧠 KEY OOP TAKEAWAYS

1. Encapsulation

Protect internal state with properties and name mangling. Use @property for computed/validated access.

2. Inheritance & MRO

Method Resolution Order (C3 linearization) determines which parent method is called. Mixins add reusable behavior.

3. Composition vs Aggregation

"Has‑a" relationships: composition (owned) vs aggregation (shared). Prefer composition over deep inheritance.

4. Design Patterns

Strategy, Observer, Factory, Decorator β€” each solves a specific design problem and promotes loose coupling.