Pular para o conteúdo

Boas práticas e padrões · Princípios SOLID · 5 min de leitura

S — Princípio da responsabilidade única (SRP)

Uma classe deve ter um só motivo para mudar: cada parte do código responde a um único "dono" da regra.

PedidoService → CalculadoraPreco: extrairextrair1PedidoService → PedidoRepository: extrairextrair2PedidoService → Notificador: extrairextrair3PedidoService — calcula, salva, envia e-mailPedidoServicecalcula, salva, envia e-mailCalculadoraPreco — regra do financeiroCalculadoraPrecoregra do financeiroPedidoRepository — regra do bancoPedidoRepositoryregra do bancoNotificador — regra do marketingNotificadorregra do marketing

Arraste para o lado para ver o diagrama inteiro.

A classe que fazia tudo vira três peças pequenas; cada pacote mostra uma responsabilidade saindo para o seu lugar.

O que diz

Robert C. Martin resume: "reúna o que muda pelos mesmos motivos e separe o que muda por motivos diferentes". "Responsabilidade" não é "fazer uma coisa só", e sim responder a um único ator: o financeiro muda a regra do preço, o DBA muda a persistência, o marketing muda o e-mail.

Como perceber a violação

  • A classe tem muitos imports de áreas diferentes (HTTP, SQL, e-mail, PDF…).
  • Mudanças de times diferentes caem sempre no mesmo arquivo e geram conflitos de merge.
  • Para testar um cálculo você precisa subir banco e servidor de e-mail.

Na prática

Extraia cada responsabilidade para uma classe com nome claro e deixe uma classe coordenadora fina (um caso de uso) chamando as três. Não exagere: dividir uma classe de 40 linhas em dez arquivos só aumenta o custo de leitura.

No código: antes e depois

✗ AntesKotlin
class PedidoService(private val db: Database, private val smtp: Smtp) {
  fun fechar(pedido: Pedido) {
    // regra do financeiro
    val total = pedido.itens.sumOf { it.preco * it.qtd } * 0.95
    // regra do banco
    db.execute("UPDATE pedidos SET total = ? WHERE id = ?", total, pedido.id)
    // regra do marketing
    smtp.send(pedido.email, "Pedido fechado: R$ $total")
  }
}
✓ DepoisKotlin
class CalculadoraPreco { fun total(p: Pedido) = p.itens.sumOf { it.preco * it.qtd } * 0.95 }
interface PedidoRepository { fun salvarTotal(id: Long, total: Double) }
interface Notificador { fun pedidoFechado(p: Pedido, total: Double) }

class FecharPedido(
  private val preco: CalculadoraPreco,
  private val repo: PedidoRepository,
  private val avisar: Notificador,
) {
  operator fun invoke(p: Pedido) {
    val total = preco.total(p)
    repo.salvarTotal(p.id, total)
    avisar.pedidoFechado(p, total)
  }
}

O caso de uso FecharPedido só coordena. A regra de preço é testada sozinha, e trocar SMTP por push não toca no cálculo.

Em resumo

Vantagens e custos

Vantagens
  • Código mais fácil de testar e de entender.
  • Mudanças localizadas, menos efeitos colaterais.
Custos e riscos
  • Mais arquivos e indireção.
  • Dividir cedo demais cria abstrações sem uso.

Referências

Continue aprendendo

Todos os temas de boas práticas e padrões →