1. Introduction to Enterprise Architecture & Low-Coupling Design Patterns
In previous lessons, we mastered Object-Oriented Programming (OOP) fundamentals—including constructors, methods, encapsulation via properties, inheritance hierarchies, and magic methods. However, as applications scale to enterprise production levels, relying excessively on deep, rigid class inheritance trees often leads to fragile architectures. A single change in a base parent class can break dozens of tightly-coupled child subclasses unexpectedly.
Modern software design principles favor Loose Coupling and Strong Cohesion. Software architects follow a fundamental design motto: "Favor Object Composition over Class Inheritance."
In this lesson, we will explore three advanced structural paradigms in Python:
- Composition ("Has-A" Relationships): Building complex systems by combining smaller, independent component objects rather than extending deep class hierarchies.
- Abstract Base Classes (ABCs): Enforcing formal interface contracts at runtime using Python's built-in
abcmodule. - Protocols (`typing.Protocol`): Implementing modern **Structural Subtyping** (static duck typing) without requiring explicit inheritance.
2. Composition vs Inheritance: "Has-A" vs "Is-A"
1. The Inheritance Pitfall ("Is-A" Relationship)
Inheritance models an "Is-A" relationship. For instance, a SavingsAccount is a BankAccount. However, using inheritance to share utility behaviors (e.g., making Car inherit from Engine or Logger) creates rigid class coupling where child classes are forced to inherit unnecessary internal methods.
2. The Composition Solution ("Has-A" Relationship)
Composition models a "Has-A" relationship. A Car has an Engine and has a GPS. Instead of deriving from parent classes, a composite class accepts references to independent component objects and delegates specialized work to them.
- Coupling Level
- Tight Coupling (Child is tightly bound to Parent implementation details).
- Loose Coupling (Class interacts with components via clear interfaces).
- Runtime Flexibility
- Static (Class hierarchy fixed at compile/definition time).
- Dynamic (Components can be swapped at runtime via dependency injection).
- Code Reusability
- Reuses code via base class extension.
- Reuses code by assembling independent, specialized modules.
- Primary Risk
- Fragile Base Class problem (modifying parent breaks sub-classes).
- Slightly more initial setup code for delegating calls.
| Architectural Dimension | Object Inheritance ("Is-A") | Object Composition ("Has-A") |
|---|---|---|
# Component Class 1: Engine Behavior
class Engine:
def __init__(self, horsepower: int):
self.horsepower = horsepower
def start(self) -> str:
return f"V8 Engine ({self.horsepower} HP) roaring to life!"
# Component Class 2: GPS Navigation Behavior
class GPSNavigation:
def route_to(self, destination: str) -> str:
return f"GPS calculating optimal route to '{destination}'..."
# Composite Class: Assembles independent components ("Has-A" Engine & GPS)
class Vehicle:
def __init__(self, make: str, model: str, engine_obj: Engine, gps_obj: GPSNavigation):
self.make = make
self.model = model
# Storing component references in instance memory
self.engine = engine_obj
self.gps = gps_obj
def drive_to(self, destination: str):
print(f"=== Operating {self.make} {self.model} ===")
print(self.engine.start()) # Delegating engine logic
print(self.gps.route_to(destination)) # Delegating routing logic
# Testing Composition via Dependency Injection
v8_engine = Engine(450)
garmin_gps = GPSNavigation()
my_car = Vehicle("Ford", "Mustang", v8_engine, garmin_gps)
my_car.drive_to("San Francisco")
=== Operating Ford Mustang === V8 Engine (450 HP) roaring to life! GPS calculating optimal route to 'San Francisco'...
3. Formal Interfaces: Abstract Base Classes (`abc.ABC`)
When designing framework APIs or plugin systems, architects must guarantee that every concrete plugin or database driver implements specific required methods. If a developer forgets to implement a required method in a subclass, errors will occur at runtime when the method is invoked.
To enforce explicit interface contracts at instantiation time, Python provides the `abc` (Abstract Base Class) module.
1. Core Rules of Abstract Base Classes
- An Abstract Base Class inherits from
abc.ABC. - Methods that MUST be implemented by subclasses are decorated with
@abstractmethod. - An Abstract Base Class **CANNOT be instantiated directly**! Attempting
base = AbstractClass()raises an immediateTypeError. - Any concrete child subclass MUST override and implement ALL
@abstractmethodmethods. If even one abstract method remains un-implemented, Python prevents subclass instantiation!
from abc import ABC, abstractmethod
# Abstract Base Class Interface Blueprint
class BaseRepository(ABC):
@abstractmethod
def save(self, record_id: str, data: dict) -> bool:
"""Abstract method: Must be implemented by concrete subclasses."""
pass
@abstractmethod
def fetch(self, record_id: str) -> dict:
"""Abstract method: Must be implemented by concrete subclasses."""
pass
# Concrete Subclass 1: In-Memory Implementation
class MemoryRepository(BaseRepository):
def __init__(self):
self._store = {}
def save(self, record_id: str, data: dict) -> bool:
self._store[record_id] = data
print(f"[MEMORY REPO] Saved record #{record_id}")
return True
def fetch(self, record_id: str) -> dict:
return self._store.get(record_id, {})
# Verifying ABC Instantiation Rules
try:
# 1. Attempting to instantiate ABC directly fails!
base_repo = BaseRepository()
except TypeError as err:
print("Caught Direct ABC Instantiation Error:", err)
# 2. Instantiating valid concrete subclass succeeds cleanly
repo = MemoryRepository()
repo.save("REC_101", {"name": "Alice", "role": "Admin"})
print("Fetched Record:", repo.fetch("REC_101"))
Caught Direct ABC Instantiation Error: Can't instantiate abstract class BaseRepository with abstract methods fetch, save
[MEMORY REPO] Saved record #REC_101
Fetched Record: {'name': 'Alice', 'role': 'Admin'}
4. Modern Structural Subtyping: Protocols (`typing.Protocol`)
Abstract Base Classes rely on Nominal Subtyping: a class MUST explicitly inherit from ABC (e.g., class MyRepo(BaseRepository):). This requires classes to share an explicit inheritance tree.
Python 3.8+ introduced Protocols (`typing.Protocol`), which bring Structural Subtyping (known as static duck typing) to Python's type system!
Protocol, Python's type checkers and runtime matchers treat it as a valid compliant implementation automatically!
from typing import Protocol, runtime_checkable
# Defining a Structural Protocol Interface
@runtime_checkable # Allows isinstance() checks on the Protocol at runtime!
class PrintablePayload(Protocol):
def format_payload(self) -> str:
"""Any class implementing format_payload() satisfies this Protocol implicitly!"""
...
# Class A: Unrelated standalone class (No inheritance!)
class InvoiceDocument:
def __init__(self, invoice_id: str, amount: float):
self.invoice_id = invoice_id
self.amount = amount
def format_payload(self) -> str:
return f"INVOICE #{self.invoice_id}: Total Amount ${self.amount:,.2f}"
# Class B: Unrelated standalone class
class AuditLogEntry:
def __init__(self, action: str, timestamp: str):
self.action = action
self.timestamp = timestamp
def format_payload(self) -> str:
return f"LOG [{self.timestamp}]: Action '{self.action}' executed."
# Polymorphic pipeline function accepting any PrintablePayload Protocol object
def render_printable_stream(items: list[PrintablePayload]):
print("=== Streaming Protocol-Compliant Payloads ===")
for item in items:
# Runtime protocol compatibility check enabled by @runtime_checkable
if isinstance(item, PrintablePayload):
print("Rendered Output:", item.format_payload())
inv = InvoiceDocument("INV_9901", 1250.00)
log = AuditLogEntry("USER_LOGIN", "2026-07-26 19:30:00")
render_printable_stream([inv, log])
=== Streaming Protocol-Compliant Payloads === Rendered Output: INVOICE #INV_9901: Total Amount $1,250.00 Rendered Output: LOG [2026-07-26 19:30:00]: Action 'USER_LOGIN' executed.
5. Combining Abstract Interfaces & Protocols with `match-case`
Python 3.10+ Class Pattern Matching integrates seamlessly with Abstract Classes and Runtime-Checkable Protocols. Structural dispatchers inspect instance compatibility against abstract interface boundaries dynamically.
from abc import ABC, abstractmethod
from typing import Protocol, runtime_checkable
@runtime_checkable
class Encryptable(Protocol):
def encrypt(self) -> str: ...
class BaseMessage(ABC):
def __init__(self, sender: str):
self.sender = sender
class SecureTextMessage(BaseMessage):
def __init__(self, sender: str, raw_text: str):
super().__init__(sender)
self.raw_text = raw_text
def encrypt(self) -> str:
return f"SECURE_HASH({self.raw_text[::-1]})"
def dispatch_interface_payload(payload_obj: object):
"""
Routes objects based on Abstract Classes and Protocols using Structural Pattern Matching.
Demonstrates evaluating interface compliance dynamically.
"""
match payload_obj:
case Encryptable() as enc_obj if isinstance(enc_obj, BaseMessage):
return f"MATCH_BOTH: BaseMessage from '{enc_obj.sender}' encrypted -> {enc_obj.encrypt()}"
case Encryptable() as enc_obj:
return f"MATCH_PROTOCOL_ONLY: Encrypted string -> {enc_obj.encrypt()}"
case BaseMessage(sender=src):
return f"MATCH_BASE_ONLY: Unencrypted BaseMessage from '{src}'"
case _:
return "ERROR: Unrecognized payload schema."
msg1 = SecureTextMessage("Alice", "TopSecret123")
print(dispatch_interface_payload(msg1))
MATCH_BOTH: BaseMessage from 'Alice' encrypted -> SECURE_HASH(321terceSTpoT)
6. Frequently Asked Interview Questions with Answers
TypeError stating that the class cannot be instantiated with abstract methods un-implemented.
ABC). Protocols rely on **Structural Subtyping** (static duck typing); any class implementing the methods and signatures specified in a Protocol satisfies the Protocol interface automatically without needing explicit inheritance.
@runtime_checkable decorator allows Python's runtime engine to perform isinstance(obj, Protocol) checks by evaluating if the object contains required methods.
7. Homework & Practical Assignments
Task 1: Composition-Based Computer System Builder
Create a script named composition_computer_task.py inside your lesson_37 folder:
- Define component class
Processor(brand: str, cores: int)with methodget_specs(). - Define component class
StorageDrive(capacity_gb: int)with methodget_specs(). - Define composite class
Computer(model: str, cpu: Processor, disk: StorageDrive). - Add method
display_system_summary()onComputerdelegating calls to component objects. - Instantiate components and composite objects, then print formatted summaries using f-strings.
Task 2: Abstract Payment Repository with `match-case` Dispatcher
Create a script named abstract_repo_task.py:
- Import
ABCandabstractmethodfromabc. - Define ABC
BasePaymentProcessor(ABC)with abstract methodexecute_transaction(amount: float) -> bool. - Create concrete subclasses
StripeProcessorandPayPalProcessorimplementing transaction logic. - Write a dispatcher function using
match-caseClass Pattern Matching to inspect concrete processor objects and execute payments safely. - Test function with instances of both concrete processors and catch direct ABC instantiation errors.
Task 3: Master Capstone Review Project — Modular Enterprise Architecture OS (`enterprise_arch_os.py`)
Create a script named enterprise_arch_os.py inside your lesson_37 folder. This assignment tests and integrates **ALL concepts learned across Lessons 1 through 37**.
Project Architectural Requirements Specification:
- Composition, ABCs & Protocols Architecture (Lesson 37):
- Define Abstract Base Class
BaseStorageEngine(ABC)with abstract methodssave_payload()andload_payload(). - Define a runtime-checkable Protocol
AuditLoggerProtocol(Protocol)specifyinglog_audit_event(). - Build concrete composite class
EnterpriseServiceOSthat composes a storage engine component and a protocol-compliant audit logger ("Has-A" relationship).
- Define Abstract Base Class
- Data Classes, Magic Methods & Inheritance (Lessons 34-36):
- Define a Data Class
ServiceRecord(record_id, title, price, status)with overloaded__lt__comparison for sorting. - Protect internal properties using
@propertygetters/setters with validation logic.
- Define a Data Class
- Advanced OOP Methods & Constructors (Lessons 32-33):
- Implement class method factory
from_json_string(cls, json_str)to construct instances. - Implement static utility methods for record ID validation. Track total active instances via class attributes.
- Implement class method factory
- Observability & Diagnostics (Lesson 31):
- Configure a
loggingFileHandler writing diagnostic logs toenterprise_arch.log. - Incorporate developer assertions (
assert) verifying component invariant non-nullability.
- Configure a
- Context Managers & Fault Tolerance (Lessons 29-30):
- Build a class-based context manager
SystemVaultLock(db_path)managing persistent storage locks. - Define custom exception classes:
ArchitectureError(Exception),StorageFailureError(ArchitectureError). Wrap executions in 4-blocktry-except-else-finallysuites.
- Build a class-based context manager
- CSV & JSON Persistence Layer (Lessons 27-28):
- Maintain storage directory
arch_vault/usingpathlib.Path. - Persist JSON database maps to
records_db.jsonand export audit logs tosystem_audit.csv.
- Maintain storage directory
- Nested Collections, Comprehensions & Data Structures (Lessons 21-26):
- Maintain an in-memory database mapping record IDs to Data Class Instances.
- Use List and Dictionary Comprehensions to clean, filter, and map record streams dynamically.
- Advanced Function Parameters & Scope (Lessons 15-17):
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
*args/**kwargslogging.
- Structure logic into pure modular functions with type hints, docstrings, early return Guard Clauses, and
- Indefinite & Definite Loops (Lessons 13-14):
- Run the interactive CLI interface inside a continuous
while Truemenu loop. - Iterate through output reports using
forloops withenumerate()and.items().
- Run the interactive CLI interface inside a continuous
- Pattern Matching CLI Dispatcher (Lesson 12):
- Process user CLI command tokens inside a
match-caseblock:case ["POST", rid, title, price_str]→ InstantiateServiceRecord, validate components, and store using composite storage engine.case ["FETCH", rid]→ Load record safely and output formatted presentation.case ["AUDIT", "COMPONENTS"]→ Route storage engine objects using Class Pattern Matching and Protocol type checks.case ["EXPORT", "CSV"]→ Export active records to CSV safely.case ["EXIT" | "QUIT"]→ Terminate session safely using a sentinel flag.case _→ Output command error message.
- Process user CLI command tokens inside a
- Conditionals, Formatting & Foundations (Lessons 1-11):
- Sanitize all inputs using
.strip()and.upper(). - Format tabular reports using f-strings with field width alignment specifiers (
:<15,:>10) and currency formatting (:,.2f).
- Sanitize all inputs using
8. File & Workspace Directory Structure
Standard Course Workspace Layout:
Ensure all exercise files are stored inside their corresponding lesson directories:
python_mastery_course/
│
├── lesson_01/ ... lesson_36/
│
└── lesson_37/
├── composition_demo.py
├── abstract_class_demo.py
├── protocol_demo.py
├── match_case_interface_dispatcher.py
├── composition_computer_task.py <-- (Task 1)
├── abstract_repo_task.py <-- (Task 2)
└── enterprise_arch_os.py <-- (Task 3: Master Review Capstone)
OnlineCBT