Skip to main content

Command Palette

Search for a command to run...

Single Responsibility Principle (SRP) in Python: A Practical Guide

Updated
•3 min read•View as Markdown

Why SOLID Principles Matter

SOLID principles are the foundation of maintainable, scalable software. Today, we’ll focus on the Single Responsibility Principle (SRP), which states:

"A class should have only one reason to change."

In other words, each component should do one thing and do it well.

Violating SRP: A Common Pitfall

Let’s examine a User class that handles multiple responsibilities:

# Versión que viola SRP (pero más completa)
class User:
    def __init__(self, name: str, email: str):
        if not self._validate_email(email):
            raise ValueError("Email inválido")
        self.name = name
        self.email = email
        self._db_connection = self._connect_to_db()
        self._email_service = self._init_email_service()

    def _validate_email(self, email: str) -> bool:
        """Valida formato básico de email"""
        return "@" in email and "." in email.split("@")[1]

    def _connect_to_db(self):
        """Simula conexión a PostgreSQL"""
        print("🔌 Conectando a PostgreSQL...")
        return {"status": "connected", "db": "users_db"}

    def _init_email_service(self):
        """Simula configuración de servicio de email"""
        print("✉️ Inicializando SMTP...")
        return {"smtp_server": "smtp.example.com", "port": 587}

    def save_to_database(self):
        """Guarda usuario en DB con validación"""
        if not hasattr(self, "name") or not hasattr(self, "email"):
            raise ValueError("Datos de usuario incompletos")

        query = f"""
        INSERT INTO users (name, email) 
        VALUES ('{self.name}', '{self.email}')
        """
        print(f"💾 Ejecutando query: {query}")
        # Simulación de inserción
        return {"status": "success", "user_id": 123}

    def send_welcome_email(self):
        """Envía email con plantilla HTML"""
        email_body = f"""
        <html>
            <body>
                <h1>Bienvenido, {self.name}!</h1>
                <p>Gracias por registrarte con el email {self.email}</p>
            </body>
        </html>
        """
        print(f"📨 Enviando email a {self.email}:\n{email_body}")
        return {"status": "sent", "email": self.email}

# Uso
try:
    user = User("Ana López", "ana@example.com")
    user.save_to_database()
    user.send_welcome_email()
except ValueError as e:
    print(f"❌ Error: {e}")

Key Problems Identified

  1. Multiple Responsibilities

    • Handles validation, database operations, email delivery, and service configuration

    • Violates SRP by managing 4 distinct concerns

  2. High Coupling

    • Tightly bound to PostgreSQL and SMTP implementations

    • Changing email providers requires modifying the User class

  3. Testing Complexity

    • Requires mocking database and email services simultaneously

    • Unit tests become fragile and interdependent

  4. Maintenance Risks

    • Any change in email templating affects the core User class

    • Database schema changes force class modifications

Refactoring for SRP Compliance

We’ll split responsibilities into dedicated classes:

# ------ User Model (solo datos) ------
class User:
    def __init__(self, name: str, email: str):
        self.name = name
        self.email = email

# ------ Validación separada ------
class UserValidator:
    @staticmethod
    def validate(user: User) -> bool:
        return UserValidator._validate_email(user.email)

    @staticmethod
    def _validate_email(email: str) -> bool:
        return "@" in email and "." in email.split("@")[1]

# ------ Database Service ------
class PostgreSQLUserRepository:
    def __init__(self):
        self._connection = self._connect()

    def _connect(self):
        print("🔌 Conectando a PostgreSQL...")
        return {"status": "connected"}

    def save(self, user: User):
        query = f"INSERT INTO users (name, email) VALUES ('{user.name}', '{user.email}')"
        print(f"💾 Ejecutando: {query}")
        return {"status": "success"}

# ------ Email Service ------
class SendGridEmailService:
    def __init__(self):
        self._config = self._load_config()

    def _load_config(self):
        print("✉️ Configurando SendGrid...")
        return {"api_key": "SG.123"}

    def send_welcome(self, user: User):
        email_body = self._build_template(user)
        print(f"📨 Enviando a {user.email}:\n{email_body}")
        return {"status": "sent"}

    def _build_template(self, user: User):
        return f"""
        <html>
            <body>
                <h1>Bienvenido, {user.name}!</h1>
                <p>Tu registro fue exitoso.</p>
            </body>
        </html>
        """

# ------ Orchestrator (coordina las dependencias) ------
class UserRegistration:
    def __init__(self):
        self.validator = UserValidator()
        self.repository = PostgreSQLUserRepository()
        self.email_service = SendGridEmailService()

    def register(self, name: str, email: str):
        user = User(name, email)

        if not self.validator.validate(user):
            raise ValueError("Email inválido")

        self.repository.save(user)
        self.email_service.send_welcome(user)
        return user

# Uso
try:
    registration = UserRegistration()
    registration.register("Carlos Ruiz", "carlos@example.com")
except ValueError as e:
    print(f"❌ Error: {e}")

Key Benefits

✅ Easier maintenance: Modify database logic without touching User.
✅ Better testing: Mock dependencies independently.
✅ Higher reusability: EmailService can be used across the app.

Real-World Analogy

Think of SRP like a restaurant:

  • Chefs cook food.

  • Waiters serve customers.

  • Cashiers handle payments.

Merging all roles into one person would be chaotic—just like violating SRP!


Conclusion

SRP reduces complexity by enforcing separation of concerns. Apply it to:

  • Avoid "god objects"

  • Simplify debugging

  • Enable team scalability

Challenge: Review your codebase—can you spot SRP violations?

E

Interesante tema el que tocaste, gracias por la información

More from this blog

Functional Programming

13 posts