Welcome to Day 7—the culmination of our Object-Oriented Programming masterclass! Up to this point, you've learned the mechanics: classes, inheritance, magic methods, decorators, and object relationships.
Today is about software architecture: how to combine these tools into modular, testable, production-ready systems that remain easy to maintain as requirements evolve.
1. Core Architectural Principles 🧱
┌─────────────────────────────────────────────────────────────────────────────┐
│ CORE OOP DESIGN PRINCIPLES │
├──────────────────────────┬──────────────────────────────────────────────────┤
│ Principle │ Core Idea │
├──────────────────────────┼──────────────────────────────────────────────────┤
│ Composition over │ Prefer combining simple objects ("has-a") over │
│ Inheritance │ creating rigid, deep hierarchy trees ("is-a"). │
├──────────────────────────┼──────────────────────────────────────────────────┤
│ Loose Coupling │ Reduce direct dependencies between modules using │
│ │ interfaces (ABCs) and Dependency Injection. │
├──────────────────────────┼──────────────────────────────────────────────────┤
│ High Cohesion │ Keep classes small and focused on a single, │
│ │ well-defined responsibility (SRP). │
├──────────────────────────┼──────────────────────────────────────────────────┤
│ Object Modeling │ Map real-world domain concepts directly to │
│ │ encapsulated classes with strong invariants. │
└──────────────────────────┴──────────────────────────────────────────────────┘
2. Principle Deep Dives 💡
1. Composition over Inheritance 🧩
Inheritance binds child classes tightly to parent implementation details (the fragile base class problem). Composition connects independent components via attributes, making systems flexible and easy to modify at runtime.
INHERITANCE (Rigid & Fragile) COMPOSITION (Flexible & Swappable)
┌──────────────────┐ ┌──────────────────┐
│ BaseNotifier │ │ InventoryManager │
└────────┬─────────┘ └────────┬─────────┘
│ │ has-a
┌────────┴─────────┐ ▼
│ EmailNotifier │ ┌──────────────────┐
└──────────────────┘ │ Notifier Protocol│
└────────┬─────────┘
│
┌───────────────┴───────────────┐
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ EmailNotifier │ │ SlackNotifier │
└──────────────────┘ └──────────────────┘
2. High Cohesion vs. Low Cohesion 🎯
- Low Cohesion (Bad): A single class manages database storage, HTML rendering, validation, and email alerts.
-
High Cohesion (Good): Every class manages one thing. A
UserRepositoryhandles data, aUserValidatorhandles rules, and aNotifierhandles communication.
# ❌ LOW COHESION (Do not do this)
class UserSystem:
def save_to_db(self): ...
def validate_email(self): ...
def send_welcome_email(self): ...
def render_dashboard_html(self): ...
# ✅ HIGH COHESION (Separation of Concerns)
class UserRepository:
def save(self, user): ...
class EmailValidator:
def validate(self, email): ...
class NotificationService:
def send_welcome(self, user): ...
3. Loose Coupling via Dependency Injection 🔌
Instead of instantiating dependencies inside a class, pass (inject) them into __init__. This allows swapping implementations (e.g., swapping a real database for a test mock) without touching internal logic.
# ❌ TIGHTLY COUPLED
class OrderProcessor:
def __init__(self):
# Hardcoded dependency! Cannot easily switch or mock during testing.
self.notifier = EmailNotifier()
# ✅ LOOSELY COUPLED
class OrderProcessor:
def __init__(self, notifier: NotificationService):
# Injected dependency! Accepts ANY object implementing NotificationService
self.notifier = notifier
3. Top OOP Pitfalls to Avoid ⚠️
┌─────────────────────────────────────────────────────────────────────────────┐
│ COMMON OOP PITFALLS │
├─────────────────────┬───────────────────────────────────────────────────────┤
│ Pitfall │ Consequence & Remedy │
├─────────────────────┼───────────────────────────────────────────────────────┤
│ The "God Object" │ One massive class holds all state and methods. │
│ │ ➔ Split into smaller, single-responsibility classes. │
├─────────────────────┼───────────────────────────────────────────────────────┤
│ Deep Inheritance │ 4+ levels of subclassing makes code untraceable. │
│ │ ➔ Flatten hierarchies; replace with Composition. │
├─────────────────────┼───────────────────────────────────────────────────────┤
│ Primitive Obsession │ Using raw dicts/strings everywhere instead of objects.│
│ │ ➔ Encapsulate related fields into Dataclasses/Objects.│
├─────────────────────┼───────────────────────────────────────────────────────┤
│ Hidden Side-Effects │ Methods modify global state or unrelated attributes. │
│ │ ➔ Keep methods pure or explicitly bound to instance. │
└─────────────────────┴───────────────────────────────────────────────────────┘
4. Mini Project: Enterprise Inventory Management System 🏬
Let's put all 7 days of concepts together into a complete, modular Inventory Management System.
System Architecture
-
Item(Dataclass): Domain model representing inventory items. -
DiscountStrategy(ABC / Composition): Pluggable pricing algorithms. -
NotificationService(ABC / Loose Coupling): Pluggable alerting interface. -
InventoryRepository(High Cohesion): Dedicated storage interface and memory repository. -
InventoryManager(Orchestrator): Business logic layer utilizing Dependency Injection.
Step 1: Initialize Your Workspace
uv init oop_day7 && cd oop_day7
touch inventory_system.py
Step 2: Implement inventory_system.py
# inventory_system.py
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Dict, List, Optional
import time
# ==========================================
# 1. DOMAIN MODEL (Encapsulation & Dataclass)
# ==========================================
@dataclass
class InventoryItem:
"""Encapsulates item state with clear fields and validation."""
item_id: str
name: str
unit_price: float
quantity: int
reorder_threshold: int = 5
def is_low_stock(self) -> bool:
"""Domain logic method enforcing state invariants."""
return self.quantity <= self.reorder_threshold
def adjust_stock(self, amount: int) -> None:
"""Safely mutates stock levels with boundary checks."""
if self.quantity + amount < 0:
raise ValueError(f"Insufficient stock for '{self.name}'. Requested: {abs(amount)}, Available: {self.quantity}")
self.quantity += amount
# ==========================================
# 2. PLUGGABLE STRATEGIES (Composition over Inheritance)
# ==========================================
class PricingStrategy(ABC):
"""Abstract strategy interface for dynamic price calculations."""
@abstractmethod
def calculate_price(self, item: InventoryItem) -> float:
pass
class StandardPricing(PricingStrategy):
def calculate_price(self, item: InventoryItem) -> float:
return item.unit_price
class ClearancePricing(PricingStrategy):
"""Applies a 30% clearance discount."""
def calculate_price(self, item: InventoryItem) -> float:
return round(item.unit_price * 0.70, 2)
# ==========================================
# 3. NOTIFICATION INTERFACE (Loose Coupling)
# ==========================================
class NotificationService(ABC):
"""Abstract interface defining the alert contract."""
@abstractmethod
def send_alert(self, message: str) -> None:
pass
class ConsoleNotifier(NotificationService):
"""Concrete implementation printing alerts to console."""
def send_alert(self, message: str) -> None:
print(f"📢 [CONSOLE NOTIFICATION] {message}")
class EmailNotifier(NotificationService):
"""Concrete implementation simulating email dispatch."""
def __init__(self, admin_email: str):
self.admin_email = admin_email
def send_alert(self, message: str) -> None:
print(f"📧 [EMAIL SENT TO {self.admin_email}] {message}")
# ==========================================
# 4. STORAGE REPOSITORY (High Cohesion)
# ==========================================
class InventoryRepository:
"""Dedicated class handling storage and query operations."""
def __init__(self):
self._items: Dict[str, InventoryItem] = {}
def save(self, item: InventoryItem) -> None:
self._items[item.item_id] = item
def get(self, item_id: str) -> Optional[InventoryItem]:
return self._items.get(item_id)
def get_all(self) -> List[InventoryItem]:
return list(self._items.values())
# ==========================================
# 5. ORCHESTRATOR SERVICE (Dependency Injection)
# ==========================================
class InventoryManager:
"""Core manager using Dependency Injection for loose coupling."""
def __init__(
self,
repository: InventoryRepository,
notifier: NotificationService,
pricing_strategy: PricingStrategy
):
# Dependencies injected via constructor
self.repo = repository
self.notifier = notifier
self.pricing_strategy = pricing_strategy
def add_item(self, item: InventoryItem) -> None:
self.repo.save(item)
print(f"✅ Added '{item.name}' to inventory.")
def process_sale(self, item_id: str, quantity_sold: int) -> float:
item = self.repo.get(item_id)
if not item:
raise KeyError(f"Item ID '{item_id}' not found.")
# Deduct stock safely
item.adjust_stock(-quantity_sold)
# Calculate price using composed pricing strategy
unit_price = self.pricing_strategy.calculate_price(item)
total_price = unit_price * quantity_sold
print(f"🛒 Sold {quantity_sold}x '{item.name}' @ ${unit_price:,.2f} each (Total: ${total_price:,.2f})")
# Trigger notification if stock drops below threshold
if item.is_low_stock():
self.notifier.send_alert(
f"LOW STOCK WARNING: '{item.name}' (ID: {item.item_id}) has only {item.quantity} unit(s) remaining!"
)
return total_price
def display_inventory_report(self) -> None:
print("\n==========================================================")
print(" CURRENT INVENTORY REPORT ")
print("==========================================================")
for item in self.repo.get_all():
effective_price = self.pricing_strategy.calculate_price(item)
status = "⚠️ LOW STOCK" if item.is_low_stock() else "OK"
print(f" [{item.item_id}] {item.name:<18} | Stock: {item.quantity:>3} | Unit Price: ${effective_price:>7.2f} | Status: {status}")
print("==========================================================\n")
# ==========================================
# TEST EXECUTION SUITE
# ==========================================
if __name__ == "__main__":
print("--- 1. Initializing System Components ---")
# Instantiate standalone components
storage_repo = InventoryRepository()
email_notifier = EmailNotifier("inventory_admin@company.com")
clearance_pricing = ClearancePricing()
# Inject dependencies into Orchestrator
manager = InventoryManager(
repository=storage_repo,
notifier=email_notifier,
pricing_strategy=clearance_pricing
)
print("\n--- 2. Populating Initial Inventory ---")
item1 = InventoryItem(item_id="SKU-101", name="Wireless Mouse", unit_price=29.99, quantity=12, reorder_threshold=4)
item2 = InventoryItem(item_id="SKU-102", name="Mechanical Keyboard", unit_price=89.99, quantity=6, reorder_threshold=3)
manager.add_item(item1)
manager.add_item(item2)
manager.display_inventory_report()
print("--- 3. Executing Sales Transactions ---")
# Sale 1: Normal reduction
manager.process_sale("SKU-101", quantity_sold=5)
# Sale 2: Triggers low stock alert via EmailNotifier
manager.process_sale("SKU-101", quantity_sold=4)
manager.display_inventory_report()
print("--- 4. Dynamically Swapping Components at Runtime ---")
# Easily swap pricing strategy or notifier without modifying InventoryManager code!
manager.pricing_strategy = StandardPricing()
manager.notifier = ConsoleNotifier()
# Sale 3: Triggers console alert with standard pricing
manager.process_sale("SKU-102", quantity_sold=4)
manager.display_inventory_report()
Step 3: Run & Verify Execution
uv run inventory_system.py
Output Summary
--- 1. Initializing System Components ---
--- 2. Populating Initial Inventory ---
✅ Added 'Wireless Mouse' to inventory.
✅ Added 'Mechanical Keyboard' to inventory.
==========================================================
CURRENT INVENTORY REPORT
==========================================================
[SKU-101] Wireless Mouse | Stock: 12 | Unit Price: $ 20.99 | Status: OK
[SKU-102] Mechanical Keyboard| Stock: 6 | Unit Price: $ 62.99 | Status: OK
==========================================================
--- 3. Executing Sales Transactions ---
🛒 Sold 5x 'Wireless Mouse' @ $20.99 each (Total: $104.95)
🛒 Sold 4x 'Wireless Mouse' @ $20.99 each (Total: $83.96)
📧 [EMAIL SENT TO inventory_admin@company.com] LOW STOCK WARNING: 'Wireless Mouse' (ID: SKU-101) has only 3 unit(s) remaining!
==========================================================
CURRENT INVENTORY REPORT
==========================================================
[SKU-101] Wireless Mouse | Stock: 3 | Unit Price: $ 20.99 | Status: ⚠️ LOW STOCK
[SKU-102] Mechanical Keyboard| Stock: 6 | Unit Price: $ 62.99 | Status: OK
==========================================================
--- 4. Dynamically Swapping Components at Runtime ---
🛒 Sold 4x 'Mechanical Keyboard' @ $89.99 each (Total: $359.96)
📢 [CONSOLE NOTIFICATION] LOW STOCK WARNING: 'Mechanical Keyboard' (ID: SKU-102) has only 2 unit(s) remaining!
==========================================================
CURRENT INVENTORY REPORT
==========================================================
[SKU-101] Wireless Mouse | Stock: 3 | Unit Price: $ 29.99 | Status: ⚠️ LOW STOCK
[SKU-102] Mechanical Keyboard| Stock: 2 | Unit Price: $ 89.99 | Status: ⚠️ LOW STOCK
==========================================================
Top comments (0)