Object-Oriented Design & SOLID Architecture
Learn to design resilient, loosely coupled classes rather than just writing code containers. Master the 4 Pillars of OOP and the 5 SOLID Principles.
The 4 Pillars of Object-Oriented Design (OOD)
Designing classes requires applying these four fundamental pillars to construct maintainable object domain models.
Protect object state by concealing internal implementation details. Use private conventions (`_protected`, `__private`) and `@property` decorators.
Expose clean, high-level interfaces (`abc.ABC`, `@abstractmethod`) while concealing complex underlying execution logic from client code.
Share common attributes and behavior through hierarchical relationships. Use judiciously to avoid overly deep, brittle inheritance trees.
Enable uniform handling of diverse object types through abstract base contracts or Pythonic structural subtyping (`typing.Protocol`).
💎 The 5 SOLID Principles of Python Software Architecture
S — Single Responsibility Principle (SRP)
A class should have one, and only one, reason to change. Separate validation, persistence, networking, and business rules.
O — Open/Closed Principle (OCP)
Software entities should be open for extension, but closed for modification. Add behavior via polymorphism or plugin strategies.
L — Liskov Substitution Principle (LSP)
Subtypes must be substitutable for their base types without breaking application behavior or throwing unexpected exceptions.
I — Interface Segregation Principle (ISP)
Clients should not be forced to depend on methods they do not use. Prefer fine-grained, dedicated protocols over fat interfaces.
D — Dependency Inversion Principle (DIP)
High-level modules should not depend on low-level modules. Both should depend on abstractions (e.g. `UserRepository` abstract class instead of hardcoded SQLite connection).
🏛️ Architectural Case Study: Decoupling a Monolithic UserService
Notice how a naive implementation bundles multiple responsibilities into a single method, vs how a SOLID architecture decouples persistence, email dispatch, security, and token generation into single-purpose components.
class UserService:
def register_user(self, email, raw_password):
# ❌ 1. Input Validation
if "@" not in email:
raise ValueError("Invalid email")
# ❌ 2. Password Hashing
hashed_pw = f"hash_{raw_password}"
# ❌ 3. Direct SQL Database Query
db = sqlite3.connect("app.db")
db.execute("INSERT INTO users...")
# ❌ 4. Direct Email Network Call
smtplib.SMTP("smtp.app.com").sendmail(...)
# ❌ 5. Direct Token Generation
token = jwt.encode(...)
return token
class UserService:
"""High-level orchestration service obeying SRP & DIP."""
def __init__(
self,
repository: UserRepository,
email_service: EmailService,
token_service: TokenService,
hasher: PasswordHasher
) -> None:
self.repository = repository
self.email_service = email_service
self.token_service = token_service
self.hasher = hasher
def register_user(self, email: str, raw_password: str) -> dict:
hashed_pw = self.hasher.hash_password(raw_password)
user_id = self.repository.save(email, hashed_pw)
self.email_service.send_welcome_email(email)
token = self.token_service.generate_token(user_id, email)
return {"user_id": user_id, "token": token}