01 — SOLID Principles, Cohesion & Coupling
At a glance
After completing this lesson you can:
- Apply SOLID principles pragmatically to Python backend code without enterprise over-abstraction.
- Measure and improve high cohesion and low coupling across service boundaries.
- Refactor monolithic god classes into focused, single-responsibility domain services.
Time: ~50 minutes · Prerequisites: 03 — Graphs & Topological Scheduling
Reading Companion
- Book: Architecture Patterns with Python (Cosmic Python) — Harry Percival & Bob Gregory
- Chapter 1: Domain Modeling (Pure business rules, entity cohesion).
- Chapter 3: A Brief Coupling and Abstraction Interlude.
Why this matters in production
"God classes" (such as a 3,000-line UserManager that hashes passwords, sends emails, queries Postgres, formats CSVs, and charges Stripe) are impossible to test without dozens of mocks. Changing email templates accidentally breaks payment processing. Applying Single Responsibility and Dependency Inversion creates modular, testable components where changes in one domain never cascade into outages in another.
Deep dive
1. High Cohesion vs Low Coupling
- Cohesion: How closely related the responsibilities inside a single module are. High cohesion = things that change together live together.
- Coupling: How dependent different modules are on each other. Low coupling = changes inside Module A require zero code changes inside Module B.
2. Pragmatic SOLID for Python Backends
- Single Responsibility (SRP): One reason to change. Separate domain rules from I/O adapters.
- Open/Closed (OCP): Open for extension, closed for modification (e.g. plugin architectures, strategy pattern).
- Liskov Substitution (LSP): Subtypes must conform to the behavioral contract of their base type.
- Interface Segregation (ISP): Small, client-specific
Protocolinterfaces instead of bloated god interfaces. - Dependency Inversion (DIP): High-level business logic depends on abstractions, not concrete database or HTTP drivers.
Hands on
python3 phase1-cs-and-architecture/03-software-architecture/01-solid-and-cohesion/examples/01_demo.py
python3 phase1-cs-and-architecture/03-software-architecture/01-solid-and-cohesion/examples/02_trap.py
AI Trap & Code Review
# BAD AI-GENERATED CODE — THE OMNIPOTENT GOD CLASS
class OrderService:
def process_order(self, order_data):
# 1. Validation
if not order_data.get("items"): raise ValueError()
# 2. Database SQL
conn = psycopg2.connect("...")
conn.cursor().execute("INSERT INTO orders ...")
# 3. Third-party HTTP call
requests.post("https://api.stripe.com/charges", ...)
# 4. SMTP email sending
smtplib.SMTP("...").sendmail(...)
Exercises
- Rung 1 (
calculate_cohesion_ratio): Measure module method-to-attribute sharing ratio (LCOM proxy) given a class interface description. - Rung 2 (
refactor_god_class— Fix the AI Bug): Decompose the monolithicOrderProcessorinto separateOrderValidator,PaymentGateway, andNotificationServicecomponents. - Rung 3 (
PaymentStrategy Pattern): Implement Open/Closed payment handlers (CreditCardPayment,CryptoPayment) conforming to a unifiedPaymentStrategy Protocol. - Rung 4 (
InterfaceSegregation): Segregate a bloatedStorageInterfaceinto narrowReadableStorageandWritableStorageprotocols. - Rung 5 (
DependencyInversionContainer): Build a lightweight service container that wires abstractions to concrete implementations without global imports.
Wrapping up
Can you explain?
- What is the difference between high cohesion and low coupling?
- Why does the Dependency Inversion Principle make testing with in-memory Fakes straightforward?
- What is the single biggest architectural smell in a pull request?
Further Reading & References
- Martin Fowler: Microservices and the Bounded Context
- Cosmic Python (Architecture Patterns with Python)
- Google Engineering Practices: Code Review Developer Guide
- Alistair Cockburn: Hexagonal Architecture (Ports and Adapters)
Show reference solution notes
Solution & Engineering Notes — 01 SOLID & Architecture
- Pragmatic SRP: Single Responsibility does not mean one method per class. It means all methods in the class serve the same cohesive business actor or domain concept.
- Dependency Inversion in Python: By expressing interfaces with
typing.Protocol, callers can inject mocks or fakes in unit tests without forcing production classes to inherit from ABCs.
Self-check
Run python3 -m unittest phase1-cs-and-architecture/03-software-architecture/01-solid-and-cohesion/test_exercises.py -v until green, then record it here. Saved to progress/rungs.json; commit it.
Completion edits PROGRESS.md; commit it with the lesson.