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.
__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})"
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.
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
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.
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)
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.
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
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.
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}")
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.
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)
Protect internal state with properties and name mangling. Use @property for computed/validated access.
Method Resolution Order (C3 linearization) determines which parent method is called. Mixins add reusable behavior.
"Hasβa" relationships: composition (owned) vs aggregation (shared). Prefer composition over deep inheritance.
Strategy, Observer, Factory, Decorator β each solves a specific design problem and promotes loose coupling.