SRP - Single Responsibility Principle
Definição formal
Seção intitulada “Definição formal”Robert C. Martin enuncia o princípio como: “A module should have one, and only one, reason to change.” A frase “um motivo para mudar” foi frequentemente mal interpretada como “uma classe deve fazer apenas uma coisa”. Em Clean Architecture, Martin refina a definição: um módulo deve ser responsável perante um único ator. Reason to change traduz-se em fonte da mudança — e fontes de mudança são pessoas/papéis: o time financeiro, o time de RH, o DBA, o time de operações.
Portanto, SRP é essencialmente um princípio de coesão de acoplamento social: código que muda pelas mesmas razões (mesmos atores) deve ficar junto; código que muda por razões diferentes (atores diferentes) deve ser separado.
O problema que resolve
Seção intitulada “O problema que resolve”Quando uma classe atende a múltiplos atores, mudanças solicitadas por um ator podem quebrar o comportamento esperado por outro. Martin ilustra com o clássico exemplo Employee que possui calcularSalario() (dono: financeiro), reportarHoras() (dono: RH) e salvar() (dono: DBA). Se calcularSalario e reportarHoras compartilham um método utilitário horasRegulares(), uma mudança pedida pelo financeiro pode alterar silenciosamente o número que o RH reporta. Dois atores, uma classe, colisão inevitável.
Os sintomas concretos de violação de SRP:
- Colisões de merge frequentes no mesmo arquivo por times diferentes.
- Classes “faz-tudo” (
UserManager,OrderServicecom 1500 linhas) que agregam persistência, validação, formatação e regra de negócio. - Um bug numa funcionalidade força reteste de funcionalidades não relacionadas.
Como identificar violações
Seção intitulada “Como identificar violações”Trade-offs e quando não aplicar dogmaticamente
Seção intitulada “Trade-offs e quando não aplicar dogmaticamente”Levar SRP ao extremo produz fragmentação anêmica: dezenas de classes de uma linha, cada uma com um método, e uma teia de colaboradores difícil de navegar. O custo de SRP é o aumento do número de peças e da indireção. A coesão que você ganha pode ser anulada pela dispersão que você cria.
- Não separe responsabilidades que sempre mudam juntas pelo mesmo ator — isso é acoplamento coeso legítimo, e reuni-las é correto.
- Em código com baixíssima taxa de mudança (scripts, protótipos descartáveis), o investimento em separação raramente se paga.
- SRP se aplica a módulos e classes, mas também a funções: uma função gigante com muitos níveis de abstração viola o espírito do princípio.
Exemplo em Java + Spring Boot 3
Seção intitulada “Exemplo em Java + Spring Boot 3”Antes — violando SRP
Seção intitulada “Antes — violando SRP”Uma única classe mistura três atores: regra de cálculo (financeiro), persistência (DBA) e notificação (marketing/comunicação).
@Servicepublic class RelatorioPedidoService {
private final DataSource dataSource; private final JavaMailSender mailSender;
public RelatorioPedidoService(DataSource dataSource, JavaMailSender mailSender) { this.dataSource = dataSource; this.mailSender = mailSender; }
public BigDecimal calcularTotalComImpostos(Pedido pedido) { BigDecimal subtotal = pedido.itens().stream() .map(ItemPedido::valorTotal) .reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal imposto = subtotal.multiply(new BigDecimal("0.17")); return subtotal.add(imposto); }
public void salvar(Pedido pedido) { try (Connection conn = dataSource.getConnection()) { PreparedStatement ps = conn.prepareStatement( "INSERT INTO pedido (id, total) VALUES (?, ?)"); ps.setString(1, pedido.id()); ps.setBigDecimal(2, calcularTotalComImpostos(pedido)); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e); } }
public void enviarConfirmacao(Pedido pedido) { SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo(pedido.emailCliente()); msg.setSubject("Pedido confirmado"); msg.setText("Total: " + calcularTotalComImpostos(pedido)); mailSender.send(msg); }}Depois — responsabilidades separadas por ator
Seção intitulada “Depois — responsabilidades separadas por ator”public record Pedido(String id, String emailCliente, List<ItemPedido> itens) {}
public record ItemPedido(String descricao, BigDecimal valorTotal) {}@Componentpublic class CalculadoraImpostoPedido {
private static final BigDecimal ALIQUOTA = new BigDecimal("0.17");
public BigDecimal calcularTotalComImpostos(Pedido pedido) { BigDecimal subtotal = pedido.itens().stream() .map(ItemPedido::valorTotal) .reduce(BigDecimal.ZERO, BigDecimal::add); return subtotal.add(subtotal.multiply(ALIQUOTA)); }}public interface PedidoRepository { void salvar(Pedido pedido, BigDecimal total);}
@Repositorypublic class JdbcPedidoRepository implements PedidoRepository {
private final JdbcTemplate jdbcTemplate;
public JdbcPedidoRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; }
@Override public void salvar(Pedido pedido, BigDecimal total) { jdbcTemplate.update("INSERT INTO pedido (id, total) VALUES (?, ?)", pedido.id(), total); }}public interface NotificadorPedido { void notificarConfirmacao(Pedido pedido, BigDecimal total);}
@Componentpublic class EmailNotificadorPedido implements NotificadorPedido {
private final JavaMailSender mailSender;
public EmailNotificadorPedido(JavaMailSender mailSender) { this.mailSender = mailSender; }
@Override public void notificarConfirmacao(Pedido pedido, BigDecimal total) { SimpleMailMessage msg = new SimpleMailMessage(); msg.setTo(pedido.emailCliente()); msg.setSubject("Pedido confirmado"); msg.setText("Total: " + total); mailSender.send(msg); }}@Servicepublic class ProcessadorPedido {
private final CalculadoraImpostoPedido calculadora; private final PedidoRepository repository; private final NotificadorPedido notificador;
public ProcessadorPedido(CalculadoraImpostoPedido calculadora, PedidoRepository repository, NotificadorPedido notificador) { this.calculadora = calculadora; this.repository = repository; this.notificador = notificador; }
public void processar(Pedido pedido) { BigDecimal total = calculadora.calcularTotalComImpostos(pedido); repository.salvar(pedido, total); notificador.notificarConfirmacao(pedido, total); }}Cada classe agora responde a um único ator. Uma mudança na regra fiscal toca apenas CalculadoraImpostoPedido; um novo canal de notificação não arrisca a lógica de cálculo.
Diagrama
Seção intitulada “Diagrama”classDiagram
class RelatorioPedidoService {
+calcularTotalComImpostos(Pedido)
+salvar(Pedido)
+enviarConfirmacao(Pedido)
}
note for RelatorioPedidoService "ANTES: 3 atores em 1 classe"
class ProcessadorPedido {
+processar(Pedido)
}
class CalculadoraImpostoPedido {
+calcularTotalComImpostos(Pedido)
}
class PedidoRepository {
<<interface>>
+salvar(Pedido, BigDecimal)
}
class NotificadorPedido {
<<interface>>
+notificarConfirmacao(Pedido, BigDecimal)
}
ProcessadorPedido --> CalculadoraImpostoPedido
ProcessadorPedido --> PedidoRepository
ProcessadorPedido --> NotificadorPedido
Anti-padrões e erros comuns
Seção intitulada “Anti-padrões e erros comuns”Relações
Seção intitulada “Relações”- Faz parte de: SOLID
- Habilita: OCP - Open-Closed Principle (classes coesas são mais fáceis de estender sem modificar).
- Complementa: ISP - Interface Segregation Principle (segregar interfaces é o SRP aplicado ao contrato).
- Fundamenta camadas em Clean Architecture e agregados coesos em Domain-Driven Design.
- Índice: Home
Perguntas de revisão
Seção intitulada “Perguntas de revisão”- Qual é a definição refinada de “motivo para mudar” que Martin adota em Clean Architecture?
Resposta
“Motivo para mudar” significa um único ator — o grupo de pessoas ou stakeholder que solicita a mudança. Coesão em SRP é sobre agrupar código que muda pelas mesmas razões sociais/organizacionais.
- Por que separar
calcularSalario()ereportarHoras()de uma classeEmployee?
Resposta
Porque respondem a atores diferentes (financeiro vs. RH). Se compartilharem lógica, uma mudança pedida por um ator pode alterar silenciosamente o comportamento esperado pelo outro.
- Qual o principal custo de aplicar SRP de forma dogmática?
Resposta
Fragmentação e indireção excessivas: muitas classes minúsculas e uma teia de colaboradores difícil de navegar, além de dispersar código que muda junto.
- Separar um sistema em Controller/Service/Repository garante SRP?
Resposta
Não. É uma boa separação técnica, mas não garante SRP se o Service continuar atendendo a múltiplos atores. SRP é sobre razões de mudança, não sobre camadas técnicas.