O Princípio da Responsabilidade Única (SRP - Single Responsibility Principle) é a base dos princípios SOLID. Ele estabelece que uma classe deve ter um, e apenas um, motivo para mudar.
Quando ignoramos esse princípio, criamos "God Classes" (Classes Deus) componentes que sabem demais e fazem demais. Essas classes são frágeis: uma mudança no banco de dados pode quebrar a lógica de validação, ou uma alteração nas regras de negócio pode quebrar o sistema de envio de e-mails.
Neste guia, analisaremos um antipadrão comum em uma funcionalidade de Cadastro de Usuário e a refatoraremos para uma arquitetura limpa e desacoplada.
❌ O "Antes": A Violação do SRP
Aqui está um exemplo típico de código legado. Temos uma classe chamada UserService, mas ela não está agindo como um serviço; ela age como um monolito.
Por que isso é ruim:
-
Alto Acoplamento: Ela está fortemente ligada a regras de validação específicas e instruções de banco de dados (
print). - Difícil de Testar: Você não consegue testar a lógica de registro sem acionar a lógica do banco de dados.
- Múltiplos Motivos para Mudar: Se as regras de senha mudarem, este arquivo é aberto. Se o banco de dados mudar, este arquivo também é aberto.
class UserService:
def register(self, name, email, password):
# Responsabilidade 1: Lógica de Validação
if len(name) < 3:
raise ValueError("O nome é muito curto")
if "@" not in email:
raise ValueError("Email inválido")
if len(password) < 6:
raise ValueError("A senha é muito fraca")
# Responsabilidade 2: Lógica de Banco de Dados (Verificar existência)
print(f"[DB] Verificando se {email} existe...")
if email == "taken@example.com":
raise ValueError("Email já está em uso")
# Responsabilidade 3: Lógica de Persistência (Salvar)
print(f"[DB] INSERT INTO users (name, email) VALUES ('{name}', '{email}')")
# Responsabilidade 4: Lógica de Notificação
print(f"[MAIL] Enviando e-mail de boas-vindas para {email}...")
Uso
service = UserService()
service.register("Alice", "alice@example.com", "123456")
✅ A Refatoração: Separação de Preocupações
Para corrigir isso, devemos identificar as responsabilidades distintas e extraí-las para suas próprias classes.
1. A Entidade (Estrutura de Dados)
Primeiro, criamos uma representação dos dados. Esta classe não tem lógica; ela apenas armazena estado.
-
Motivo para mudar: Apenas se os atributos do usuário (como adicionar
telefone) mudarem.
from dataclasses import dataclass
@dataclass
class User:
name: str
email: str
password: str
2. O Validador (Regras de Negócio)
Extraímos as regras de validação. Esta classe garante a integridade dos dados.
- Motivo para mudar: Apenas se as regras para dados válidos mudarem.
class UserValidator:
def validate(self, user: User):
if len(user.name) < 3:
raise ValueError("O nome é muito curto")
if "@" not in user.email:
raise ValueError("Email inválido")
if len(user.password) < 6:
raise ValueError("A senha é muito fraca")
3. O Repositório (Persistência)
Abstraímos as operações de banco de dados. Esta classe lida com o "Como" armazenar os dados.
- Motivo para mudar: Apenas se a tecnologia do banco de dados (SQL, NoSQL, Arquivo) mudar.
class UserRepository:
def exists(self, email: str) -> bool:
# Simula verificação no DB
return email == "taken@example.com"
def save(self, user: User):
# Simula salvamento no DB
print(f"[DB] INSERT INTO users VALUES ('{user.name}', '{user.email}')")
4. O Serviço (O Orquestrador)
Finalmente, reescrevemos o UserService. Note como ele ficou limpo. Ele não sabe como validar ou como salvar; ele simplesmente delega essas tarefas para os especialistas (as dependências injetadas).
- Motivo para mudar: Apenas se o fluxo de trabalho do cadastro mudar (ex: adicionar uma etapa para verificar o telefone via SMS).
class UserService:
def __init__(self, validator: UserValidator, repository: UserRepository):
# Injeção de Dependência: Pedimos as ferramentas que precisamos
self.validator = validator
self.repository = repository
def register(self, name, email, password):
user = User(name, email, password)
# Passo 1: Validar
self.validator.validate(user)
# Passo 2: Verificar Regras de Negócio
if self.repository.exists(email):
raise ValueError("Email já está em uso")
# Passo 3: Persistir
self.repository.save(user)
print(">>> Usuário registrado com sucesso.")
Exemplo de Uso
Ao conectar os componentes ("wiring"), criamos um sistema flexível.
# 1. Configurar as dependências
repo = UserRepository()
val = UserValidator()
# 2. Injetar dependências no Serviço
service = UserService(validator=val, repository=repo)
# 3. Executar a lógica
try:
service.register("Bob", "bob@example.com", "senhaSegura123")
except ValueError as e:
print(f"Erro: {e}")
Conclusão
Ao aplicar o Princípio da Responsabilidade Única, transformamos um script rígido em uma arquitetura de software profissional.
Os benefícios dessa abordagem são claros:
-
Testabilidade: Agora você pode criar testes unitários para o
UserValidatorisoladamente, sem precisar de um banco de dados falso. -
Manutenibilidade: Se o esquema do banco de dados mudar, você edita apenas o
UserRepository. OUserServicepermanece intocado. -
Legibilidade: O
UserServiceagora lê como uma história de alto nível sobre o que acontece, em vez de uma lista bagunçada de como acontece.
Essa separação reduz a carga cognitiva sobre o desenvolvedor e garante que sua aplicação seja robusta o suficiente para lidar com mudanças futuras com risco mínimo.
Top comments (0)