Publicado em
- 8 min read
SOLID: Pare de criar classes "Canivete Suíço" com o SRP (Single Responsibility Principle)
Introdução
Fala, dev! Tudo certo?
Se você já sentiu aquele frio na espinha ao abrir uma classe de 2.000 linhas que faz desde o cálculo do frete até o envio de SMS pro cliente, parabéns: você encontrou um violador em série do SRP (Single Responsibility Principle).
Para nos ajudar a evitar cenários de terror como esse, recorremos ao SOLID. Esses princípios funcionam como diretrizes valiosas sobre como organizar funções e estruturas de dados em nosso código. O grande objetivo por trás deles é a criação de estruturas de software que alcancem três metas fundamentais:
- Tolerem mudanças: O software precisa evoluir sem quebrar a cada alteração.
- Sejam fáceis de entender: O código deve ser claro para que outros desenvolvedores (ou você mesmo no futuro) possam trabalhar nele sem mistérios.
- Sejam a base de componentes reutilizáveis: Estruturas bem definidas podem ser aproveitadas em muitos outros sistemas de software, economizando tempo e esforço.
Neste artigo, vamos focar no primeiro e, talvez, mais fundamental desses princípios: o S do acrônimo, que representa o Single Responsibility Principle (Princípio de Responsabilidade Única).
Popularizado por Robert C. Martin (o famoso Uncle Bob), este princípio traz uma definição cirúrgica que deve guiar o design das nossas classes:
Uma classe deve ter um, e apenas um, motivo/razão para mudar.
Muita gente confunde “responsabilidade” com “fazer uma única coisa”. No mundo real, responsabilidade tem a ver com atores ou contextos. Se o seu código atende ao RH e ao Financeiro na mesma classe, quando o RH mudar uma regra, você corre o risco de quebrar o Financeiro. Isso é acoplamento perigoso.
O Erro Comum: A “Classe Canivete Suíço”
Imagine que estamos desenvolvendo um sistema de e-commerce. É muito tentador criar uma classe Pedido que resolve a vida de todo mundo. Olhe este exemplo em Java:
// ALERTA: Não faça isso em casa!
public class Pedido {
public void calcularTotal() {
// Lógica de negócio (Responsabilidade 1)
System.out.println("Calculando total...");
}
public void salvarNoBanco() {
// Persistência de dados (Responsabilidade 2)
System.out.println("Salvando pedido no MySQL...");
}
public void enviarEmailConfirmacao() {
// Comunicação/Notificação (Responsabilidade 3)
System.out.println("Enviando e-mail para o cliente...");
}
}
Por que isso é ruim?
- Dificuldade de manutenção: Se você trocar o banco de dados de MySQL para MongoDB, precisa mexer na classe de Pedido.
- Frágil: Um erro na lógica de e-mail pode impedir que o pedido seja salvo.
- Difícil de testar: Para testar o cálculo, você acaba carregando dependências de banco e e-mail.
A Solução: Dividir para Conquistar
Para aplicar o SRP, precisamos separar essas responsabilidades em classes distintas, cada uma focada no seu “motivo de mudança”.
1. A Entidade (Negócio)
A classe Pedido agora só entende de… pedidos!
public class Pedido {
private List<Item> itens;
public double calcularTotal() {
return itens.stream().mapToDouble(Item::getPreco).sum();
}
}
2. A Persistência (Repositório)
Criamos uma classe específica para lidar com o banco de dados.
public class PedidoRepository {
public void salvar(Pedido pedido) {
// Conecta ao banco e salva
System.out.println("Salvando pedido no banco de dados...");
}
}
3. A Comunicação (Serviço de Notificação)
Outra classe cuida exclusivamente do contato com o cliente.
public class EmailService {
public void enviarConfirmacao(Pedido pedido) {
// Lógica de SMTP/API de e-mail
System.out.println("E-mail enviado com sucesso!");
}
}
Benefícios Reais
Ao aplicar o SRP no seu projeto, você ganha:
- Reutilização: O EmailService pode ser usado por outras partes do sistema.
- Testabilidade: Você consegue criar testes unitários para o cálculo do pedido sem precisar de um servidor de e-mail fake.
- Clareza: Qualquer dev que entrar no projeto saberá exatamente onde encontrar a lógica de banco de dados.
Dica: Se você estiver com dificuldade de dar um nome simples para sua classe (algo como ProcessadorDePedidoPersistidorEEnviador), é um sinal claro de que ela está fazendo coisa demais!
Aprodundando no Single Responsibility Principle
No livro Arquitetura Limpa (páginas 61 a 67), Robert C. Martin reforça que o Single Responsibility Principle trata de responder a Atores comerciais distintos que solicitam mudanças em contextos diferentes. Misturar essas responsabilidades em uma única classe é a receita para o caos arquitetural.
Para ilustrar isso, seguiremos com um outro exemplo de e-commerce.
O Rascunho do Caos: A Classe “Ordem Monolítica”
Imagine uma classe Order monolítica. Ela é perigosa porque tenta servir a três “senhores” comerciais diferentes ao mesmo tempo: Finanças(CFO), Operações(COO/Logística) e Atendimento(CS).
Misturar essas regras de negócio (cálculo de frete complexo), regras de infraestrutura (etiquetas de envio) e comunicação (e-mails de branding) em um único arquivo cria uma teia de interdependências perigosa.
O diagrama abaixo compara visualmente essa estrutura monolítica com a solução organizada sugerida pela Arquitetura Limpa:

Diagrama Comparativo: Parte superior mostra o caos monolítico, onde todos os atores (Finanças, Operações, Atendimento) interferem na mesma classe Order. Parte inferior mostra a solução organizada, onde cada ator possui sua própria classe de serviço dedicada e uma fachada coordena o fluxo.
O Caos em Java (Violação do SRP)
Vamos ver como ficaria esse caos no código Java:
// A Classe Ordem Monolítica (Violando SRP)
// Esta classe tenta servir a três Atores/Responsabilidades
public class Order {
// 1. Lógica de Negócio (Servindo FINANÇAS/CFO)
public double calculateShippingCost() {
// Lógica complexa de taxação e frete
System.out.println("Finanças: Calculando frete e impostos...");
return 0;
}
// 2. Lógica de Infraestrutura (Servindo OPERAÇÕES/Logística/COO)
public String generateManifest() {
// Lógica de armazém e etiquetas de envio
System.out.println("Operações: Gerando manifesto de expedição...");
return "";
}
// 3. Comunicação (Servindo ATENDIMENTO/CS)
public void sendConfirmationEmail() {
// Lógica de branding e notificação do cliente
System.out.println("Atendimento: Enviando e-mail de confirmação...");
}
// O Vilão: Um método compartilhado
private void sharedCalculation() {
// Lógica para obter peso e dimensões (crucial para frete E logística)
System.out.println("Lógica de cálculo de peso/dimensões compartilhada.");
}
}
Sintomas da Doença: Interdependência Acidental e Conflitos
Esse design monolítico quebra de duas maneiras perigosas:
- Interdependência Acidental: O CFO quer mudar a fórmula de cálculo de peso e dimensões (sharedCalculation) para pagar menos imposto. O COO continua usando a fórmula antiga para logística. A equipe de TI muda a fórmula compartilhada para o CFO e, de repente, os manifestos de logística do COO quebram.
- Conflitos de Versão: Se dois desenvolvedores, de times diferentes (um de Finanças e outro de Operações), tentarem alterar essa mesma classe no mesmo commit, você já sabe: conflitos de mesclagem (merge conflicts) pesados e código frágil.
A Solução da Arquitetura Limpa: Separação Cirúrgica
Seguindo o princípio da Arquitetura Limpa, Uncle Bob propõe uma separação direta: cada classe responde a APENAS UM Ator. Separamos o algoritmo de cálculo de frete, do relatório de logística e da notificação do cliente em serviços dedicados. Vamos ver como ficaria essa nova estrutura organizada:
A Solução em Java (SRP Respeitado)
// A Solução da Arquitetura Limpa (SRP Respeitado)
// Cada classe responde a APENAS UM Ator/Responsabilidade
// 1. Classe para o CFO (Finanças)
public class ShippingRateCalculator {
public double calculateCost(OrderData data) {
// Lógica de frete e imposto dedicada
System.out.println("Finanças: Lógica de custo dedicada.");
return 0;
}
}
// 2. Classe para o COO (Operações)
public class ShippingManifestGenerator {
public String generateManifest(OrderData data) {
// Lógica de logística dedicada
System.out.println("Operações: Geração de manifesto dedicada.");
return "";
}
}
// 3. Classe para o ATENDIMENTO (CS)
public class ConfirmationEmailNotifier {
public void sendEmail(OrderData data) {
// Lógica de notificação dedicada
System.out.println("Atendimento: Notificação de e-mail dedicada.");
}
}
// 4. Classe Simples de Dados (Entidade)
public class OrderData {
// Apenas dados básicos da ordem, sem lógica complexa
}
// 5. Facade (Fachada) opcional para coordenar
public class OrderFacade {
private final ShippingRateCalculator calculator;
private final ShippingManifestGenerator manifestGenerator;
private final ConfirmationEmailNotifier notifier;
public OrderFacade() {
this.calculator = new ShippingRateCalculator();
this.manifestGenerator = new ShippingManifestGenerator();
this.notifier = new ConfirmationEmailNotifier();
}
public double calculateShippingCost(OrderData data) {
return calculator.calculateCost(data);
}
}
Essa separação cirúrgica resolve os problemas anteriores:
- Evita Interdependência Acidental: O algoritmo sharedCalculation() que o CFO quer mudar agora é isolado em ShippingRateCalculator. O COO continua usando sua própria lógica em ShippingManifestGenerator, e os dois não se sobrepõem acidentalmente.
- Resolve Conflitos de Mesclagem: Desenvolvedores trabalhando para o CFO mexem em ShippingRateCalculator. Desenvolvedores trabalhando para o COO mexem em ShippingManifestGenerator. Arquivos diferentes, zero conflitos.
Conclusão
Dominar o Single Responsibility Principle é o primeiro grande passo para sair do modo “apenas codar” e entrar no modo “arquitetura de software”.
Como vimos nos exemplos em Java, a separação clara de responsabilidades não é preciosismo teórico; é uma estratégia vital para garantir que seu código não colapse sob o peso de novas regras de negócio ou mudanças de infraestrutura. Ao dar às suas classes apenas um motivo para mudar, você blinda seu software contra efeitos colaterais indesejados e facilita absurdamente a criação de testes unitários confiáveis.
No SRP, não pergunte o que sua classe faz; pergunte a quem ela serve. Se ela mistura contextos comerciais diferentes para Finanças, Operações e Atendimento, divida-a. Sua sanidade (e a do seu time) agradecem.
Mas lembre-se: o SRP é apenas a base do alicerce. O acrônimo SOLID tem mais quatro letras poderosas esperando por você.
Nos próximos artigos, vamos explorar como o O (Open/Closed Principle) nos ajuda a estender funcionalidades sem alterar o código existente, e como os princípios L, I e D fecham o ciclo de um design robusto e profissional.
Fique ligado no blog!
📚 Referências
- Martin, Robert C. Arquitetura Limpa: O Guia do Artesão para Estrutura e Design de Software. 1ª ed. Alta Books, 2019. p. [61-67].