Decomposição e Responsabilidade

Aula 4 — Programação Orientada a Objetos

Autor

Marcos M. Raimundo — Instituto de Computação, UNICAMP

Data de Publicação

26 de agosto de 2026

Slides Lista de aulas

1 Proposta da Aula

Nas aulas anteriores, protegemos o objeto: encapsulamento e invariantes garantem que o estado não se corrompa; a Lei de Demeter garante que um objeto não dependa fragilmente das entranhas de seus vizinhos. Mas essas regras respondem só a uma parte do bom design — elas dizem como um objeto deve interagir, não o que ele deve fazer. Um objeto pode respeitar Demeter à risca, ter o estado perfeitamente encapsulado, e ainda assim ser responsável por absolutamente tudo.

O roteiro, em quatro perguntas:

  1. Como reconhecer, no próprio código, uma classe que “sabe demais” ou “faz demais”?
  2. O que exatamente medem coesão e acoplamento, e por que quase sempre andam juntos?
  3. Quando a solução — dividir responsabilidades — vira ela mesma um problema?
  4. Como a linguagem já distingue, semanticamente, entre “usar”, “ter” e “ser parte de” outro objeto?

2 A Classe Deus

God Class (Classe Deus, ou Objeto Onisciente) é uma classe que sabe demais ou faz demais. É comum, no início do aprendizado, confundir “modelar um objeto do mundo real” com “criar um gerente que centraliza tudo”:

public class Pedido {
    private List<Item> itens;
    private Cliente cliente;

    // Responsabilidade 1: Regra de Negocio (Core)
    public double calcularTotal() { /* soma itens... */ }

    // Responsabilidade 2: Logica de Impostos (Externo)
    public double calcularICMS() {
        if (cliente.getEstado().equals("SP")) return calcularTotal() * 0.18;
        return calcularTotal() * 0.12;
    }

    // Responsabilidade 3: Formatacao e UI (Apresentacao)
    public String gerarReciboTexto() {
        return "Cliente: " + cliente.getNome() + " Total: " + calcularTotal();
    }

    // Responsabilidade 4: Persistencia (Infraestrutura)
    public void salvarNoBanco() { /* JDBC, conexao, transacao... */ }

    // Responsabilidade 5: Comunicacao (Rede)
    public void enviarConfirmacaoEmail() { /* SMTP, credenciais... */ }
}

Quatro razões pelas quais esse design é ruim:

  1. Fragilidade extrema: se a regra de imposto muda, altera-se Pedido; se o banco muda de MySQL para MongoDB, altera-se Pedido; se o layout do recibo muda, altera-se Pedido. Cada alteração arrisca introduzir bugs em partes não relacionadas.
  2. Acoplamento rígido com infraestrutura: Pedido está “sujo” com SQL e SMTP — impossível testar o cálculo do total sem um banco de dados ativo.
  3. Dificuldade de reuso: a lógica de e-mail, enterrada dentro de Pedido, não pode ser reaproveitada em outro lugar (o cadastro de usuários, por exemplo).
  4. Carga cognitiva alta: entender o que um Pedido faz exige ler centenas de linhas de infraestrutura que nada têm a ver com o domínio.

Em suma, a Classe Deus viola a Separação de Preocupações: concentra todas as razões de mudança num único ponto, tornando o sistema rígido.

3 Coesão e Acoplamento

Duas métricas de qualidade caminham de mãos dadas — aumentar a coesão de uma classe costuma reduzir o acoplamento do sistema.

Coesão mede o foco interno: o quanto as responsabilidades de uma classe pertencem juntas. Uma caixa de ferramentas só com chaves de fenda é altamente coesa; a mesma caixa com um sanduíche, um sapato e um controle remoto tem baixa coesão — ela tenta ser várias coisas ao mesmo tempo.

Acoplamento mede o vínculo externo: o quanto uma classe conhece ou depende das entranhas de outra. Se, para ligar a TV da sala, você precisasse primeiro abrir a geladeira e apertar um botão lá dentro, isso seria alto acoplamento — dois sistemas independentes amarrados sem necessidade.

“Alta coesão nos diz que o objeto é uma unidade lógica sólida; baixo acoplamento nos diz que esse objeto é livre para evoluir sem quebrar o mundo ao seu redor.”

O objetivo do design modular é sempre alta coesão e baixo acoplamento — e a ferramenta para chegar lá é o assunto do próximo bloco.

4 SRP e o Teste do “E”

O Princípio da Responsabilidade Única (SRP): uma classe deve ter uma, e apenas uma, razão para mudar. Uma heurística simples e poderosa para diagnosticar violações: descreva o que a classe faz numa única frase. Se você precisar de um “E” (ou “OU”), a classe tem responsabilidades demais.

  • “A classe Usuario valida a senha E salva os dados no banco.”
  • “A classe Relatorio formata os dados em PDF E envia por FTP.”
  • “A classe Pedido calcula o total E processa o pagamento E notifica o cliente.”
public class Pedido {
    private Carrinho carrinho;
    // Faz o calculo E processa o pagamento E envia e-mail
    public void finalizarPedido() {
        double total = carrinho.getTotal();                            // 1. Calculo
        System.out.println("Processando R$ " + total + " no cartao..."); // 2. Pagamento (misturado)
        System.out.println("Enviando e-mail para o cliente...");         // 3. Notificacao (misturado)
    }
}

Imagine tentar testar só o cálculo do total: seria impossível sem acidentalmente disparar um e-mail ou tentar cobrar um cartão de verdade — as três lógicas estão fortemente acopladas no mesmo método.

A solução: extrair especialistas e delegar. Já temos Pagavel (Aula 3); criamos ServicoEmail:

public class Pedido {
    private Carrinho carrinho;
    private Pagavel metodoPagamento;   // Abstracao de Pagamento
    private ServicoEmail emailService; // Nova classe especialista

    // Inversao de Controle: injetamos as dependencias no construtor
    public Pedido(Carrinho carrinho, Pagavel pagavel, ServicoEmail email) {
        this.carrinho = carrinho;
        this.metodoPagamento = pagavel;
        this.emailService = email;
    }

    public void finalizar() { // sempre melhor delegar
        metodoPagamento.processar(carrinho.getTotal());
        emailService.enviarConfirmacao(carrinho.getCliente());
    }
}

O Pedido deixa de ser “faz-tudo” e vira “gerente” — não executa a tarefa, delega para quem sabe. Essa Injeção de Dependência (DI) via construtor traz três ganhos imediatos:

  1. Testabilidade: um teste pode injetar um PagavelMock e um EmailMock — a lógica de finalizar() é verificada sem cobrar cartão de verdade nem enviar spam.
  2. Reuso: ServicoEmail pode ser usado por outras classes (RecuperarSenha, por exemplo), já que não está mais preso dentro de Pedido.
  3. Manutenibilidade: um bug no pagamento fica isolado nas classes de pagamento, sem risco de quebrar o envio de e-mails.
DicaPor que testar Pedido.finalizar() sem Injeção de Dependência exigiria cobrar um cartão de crédito de verdade?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

5 SRP Avançado: a Teoria do Ator e a Métrica LCOM

Robert C. Martin refinou o SRP: “uma razão para mudar” era frequentemente mal interpretada. A verdadeira fonte de mudança não são algoritmos, são pessoas — os atores ou departamentos que pedem a alteração. Se uma classe atende dois departamentos diferentes, ela tem duas razões para mudar:

public class Funcionario {
    private double salarioBase;
    private int horasExtras;

    // O CFO solicita e altera esta logica
    public double calcularPagamento() { return salarioBase + (horasExtras * 50); }

    // O RH solicita alteracoes no formato do relatorio
    public String gerarRelatorioHoras() { return "Total de Horas: " + horasExtras + "h"; }
}

Misturar essas lógicas acopla os ciclos de lançamento do financeiro e do RH — uma mudança de layout de relatório força recompilar e re-testar a folha de pagamento.

Existe até uma prova matemática de falta de coesão: o LCOM (Lack of Cohesion in Methods), que analisa a interseção de uso de atributos pelos métodos:

public class GestorDeUsuario {
    private String nome;        // usado APENAS por salvarNome()
    private String ipConexao;   // usado APENAS por logAcesso()

    public void salvarNome(String n) { this.nome = n; /* persistencia */ }
    public void logAcesso(String ip) { this.ipConexao = ip; /* auditoria */ }
}

Os métodos não compartilham estado algum — a interseção de uso é vazia. Isso é o código implorando para virar duas classes: RepositorioUsuario e AuditoriaAcesso.

6 O Lado Sombrio do SRP

O SRP aplicado sem pragmatismo pode destruir a legibilidade do sistema. Dois riscos:

  • Cirurgia de Espingarda (Shotgun Surgery): a separação é tão excessiva que implementar uma única regra de negócio exige abrir e modificar 15 classes minúsculas diferentes.
  • Modelo Anêmico (revisitado da Aula 2, agora em escala arquitetural): retirar toda a responsabilidade do objeto, reduzindo-o a um contentor passivo:
// RUIM: fragmentacao excessiva (Modelo Anemico)
public class Conta {
    public double saldo; // estado exposto, sem comportamento
}
public class TransferenciaService {
    public void transferir(Conta origem, Conta destino, double valor) {
        origem.saldo -= valor;
        destino.saldo += valor;
    }
}

// BOM: SRP equilibrado (Modelo Rico)
public class Conta {
    private double saldo;
    public void debitar(double valor) { /* valida e deduz */ }
    public void creditar(double valor) { /* adiciona */ }
}

O SRP não proíbe que Conta gerencie seu próprio saldo — pelo contrário, assegurar essa integridade é a responsabilidade primária dela. A regra de equilíbrio: agrupe o que muda junto e pelos mesmos motivos; separe apenas o que muda em ritmos ou por atores diferentes.

7 Tipos de Associação

Proteger o estado e aplicar SRP não basta — é preciso conectar as peças. Três níveis de força estrutural definem quem instancia e quem é coletado junto com quem:

Dependência (“Usa um”) — o vínculo mais fraco: um objeto usa outro temporariamente, sem guardar sua referência.

public class Carrinho {
    // O Carrinho DEPENDE da lista temporaria para conferir,
    // mas nao a "guarda" para si (nao ha this.lista = ...)
    public boolean confereTotal(ArrayList<Produto> itensVerificacao) {
        double soma = 0.0;
        for (Produto p : itensVerificacao) soma += p.precoFinal();
        return soma == this.total;
    }
}

Agregação (“Tem um”) — vínculo todo-parte, mas com ciclos de vida independentes: a parte sobrevive à destruição do todo.

public class Carrinho {
    private ArrayList<Produto> itens; // o Carrinho "tem" Produtos
    public void adicionarItem(Produto p) { this.itens.add(p); }
}
// Se o carrinho for destruido, o Smartphone ainda existe no catalogo!
carrinho = null; // Carrinho morre; Produto sobrevive.

Composição (“É parte de”) — a forma mais forte: a parte pertence unicamente ao todo, e morre com ele (como um documento e suas páginas).

public class Cliente {
    private Cartao cartao;
    // Cliente nasce e gera seu proprio cartao; ninguem mais tem acesso direto a ele
    public Cliente(String nome, double limiteInicial) {
        this.nome = nome;
        this.cartao = new Cartao(limiteInicial); // ciclo de vida atrelado
    }
}

A escolha entre Agregação e Composição é, muitas vezes, uma decisão de domínio de negócio, não só mecânica: se a conta de um cliente é excluída, os dados do cartão de crédito privado devem sumir junto (Composição), não flutuar soltos no sistema (o que uma Agregação permitiria).

DicaPor que a escolha entre Agregação e Composição para Cliente/Cartao depende de uma decisão de negócio, e não só de como o código é escrito?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

8 Delegação: o Motor da Composição

Associação define a estrutura (quem contém quem); Delegação define o comportamento (quem faz o quê com o que contém). Relembrando a violação de Demeter da Aula 3:

Cliente joao = new Cliente("Joao");
joao.cadastrarCartao(new Cartao(1000.0));
// O Principal sabe demais! Ele "rouba" o cartao do cliente
// para processar o pagamento por conta propria.
joao.getCartao().processar(500.0);

A cura não é esconder o Cartao atrás de um getter mais discreto — é impedir que qualquer um precise dele diretamente:

public class Cliente {
    private String nome;
    private Cartao cartao;

    public void pagar(double valor) {
        // 1. Defesa da maquina de estados (Fail-Fast)
        if (this.cartao == null) {
            throw new IllegalStateException("Cliente sem cartao.");
        }
        // 2. DELEGACAO: o Cliente nao faz operacoes matematicas.
        // Ele repassa o comando ao especialista financeiro.
        this.cartao.processar(valor);
    }
}

Antes: joao.getCartao().processar(500.0); — depois: joao.pagar(500.0);. O chamador agora desconhece completamente a existência de Cartao. Associação (ter um objeto) mais Delegação (usar o objeto) é a base para trocar componentes sem impacto no sistema — e é o mecanismo que torna a composição, segundo o princípio “favoreça composição em vez de herança”, tão poderosa quanto a herança, porém muito mais flexível.

Dicajoao.pagar(500.0) esconde a existência de Cartao de quem chama. Isso é o mesmo mecanismo da Lei de Demeter (Aula 3) ou algo diferente?

Escreva sua resposta e compare com um colega antes de avançar (2 min).

9 Conclusão

Voltando às quatro perguntas da abertura:

  1. Reconhecer uma Classe Deus: ela precisa de “E” para ser descrita numa frase, ou mistura razões de mudança de atores diferentes.
  2. Coesão e acoplamento: coesão é o quanto uma classe é uma unidade lógica sólida; acoplamento é o quanto ela depende de vizinhos — as duas costumam melhorar juntas.
  3. Quando dividir vira problema: quando a fragmentação exige tocar em muitas classes para uma única regra (Cirurgia de Espingarda), ou esvazia objetos de comportamento (Modelo Anêmico).
  4. Usar, ter, ser parte de: Dependência (temporário), Agregação (ciclos de vida independentes), Composição (ciclos de vida atrelados) — e a Delegação é o que torna essas estruturas vivas.

Ponte para a Aula 5

A delegação de hoje usou uma interface já pronta (Pagavel, da Aula 1). A Aula 5 formaliza a interface como contrato de verdade, e introduz uma ferramenta que temos usado implicitamente — testes automatizados com mocks — de forma explícita, com JUnit.

10 Exercícios

10.1 Questões discursivas

  1. A classe Pedido do início desta aula tinha cinco responsabilidades (cálculo, imposto, formatação, persistência, e-mail). Escolha duas delas e explique, para cada uma, qual “ator” (departamento ou stakeholder) provavelmente solicitaria mudanças nela — e por que isso já é evidência de violação do SRP.

  2. Um colega decide aplicar o SRP “ao pé da letra” e separa a classe Pedido em 12 classes de uma linha cada, uma para cada campo. Explique, usando os conceitos de Cirurgia de Espingarda e Modelo Anêmico, por que essa fragmentação pode ser pior do que a Classe Deus original.

  3. Explique a diferença entre Agregação e Composição usando o exemplo de Cliente e Cartao. Por que essa escolha de design depende de uma decisão sobre o domínio de negócio (o que acontece com o cartão quando a conta é excluída), e não apenas de como o atributo é declarado em código?

10.2 Questões de Verdadeiro/Falso

Cada bloco de 4 itens trata do mesmo tema. A questão só é considerada correta se todos os 4 itens forem julgados corretamente (deixar em branco tem penalidade de 20% da nota da questão).

DicaA Classe Deus (God Class)
  1. ( ) É identificada por centralizar lógicas de múltiplos atores (financeiro, infraestrutura, UI) num único arquivo.
  2. ( ) O “Teste do E” ajuda a diagnosticá-la: se a classe faz X E Y, provavelmente tem responsabilidades demais.
  3. ( ) Uma Classe Deus facilita a manutenção, pois toda a lógica fica concentrada num só lugar.
  4. ( ) Testar isoladamente uma responsabilidade de uma Classe Deus costuma exigir efeitos colaterais indesejados (e-mail, banco).
DicaCoesão e Acoplamento
  1. ( ) Coesão mede o quão focadas e inter-relacionadas são as responsabilidades internas de uma classe.
  2. ( ) O objetivo de um bom design é alto acoplamento e baixa coesão.
  3. ( ) Uma classe de baixa coesão tenta ser várias coisas ao mesmo tempo.
  4. ( ) Aumentar a coesão de uma classe costuma facilitar a redução do acoplamento do sistema.
DicaSRP e o Teste do “E”
  1. ( ) O SRP afirma que uma classe deve ter uma, e apenas uma, razão para mudar.
  2. ( ) Se a descrição de uma classe exige a conjunção “E”, ela provavelmente viola o SRP.
  3. ( ) Aplicar o SRP significa que uma classe só pode ter um único método público.
  4. ( ) Pedido que calcula total, processa pagamento e envia e-mail no mesmo método viola o SRP.
DicaInjeção de Dependência
  1. ( ) Ocorre quando um objeto recebe seus colaboradores de fora, em vez de criá-los internamente com new.
  2. ( ) A injeção via construtor garante que o objeto nunca exista sem suas dependências essenciais.
  3. ( ) A DI aumenta o acoplamento, pois obriga o objeto a conhecer quem o instanciou.
  4. ( ) Sem DI, testar Pedido.finalizar() isoladamente, sem efeitos colaterais reais, é muito mais difícil.
DicaA Teoria do Ator no SRP
  1. ( ) Segundo Robert Martin, um módulo deve ser responsável perante um, e apenas um, ator.
  2. ( ) Misturar lógica solicitada pelo RH e pelo Financeiro na mesma classe é seguro, pois são setores diferentes.
  3. ( ) O conflito de atores é uma fonte comum de “quebras colaterais” em classes com múltiplas razões de mudança.
  4. ( ) A verdadeira fonte de mudança em software costuma ser as pessoas ou departamentos que a solicitam.
DicaA Métrica LCOM
  1. ( ) LCOM avalia a falta de coesão observando se os métodos de uma classe compartilham os mesmos atributos.
  2. ( ) Um LCOM alto indica uma classe muito coesa e bem projetada.
  3. ( ) Se metade dos métodos usa o atributo A e a outra metade usa só o atributo B, a classe deveria ser dividida.
  4. ( ) LCOM é uma forma de provar matematicamente que uma classe é, na prática, duas classes disfarçadas.
DicaO Lado Sombrio do SRP
  1. ( ) A Cirurgia de Espingarda ocorre quando uma única mudança de negócio exige tocar em muitas classes minúsculas.
  2. ( ) O Modelo Anêmico é uma consequência possível da aplicação fanática e sem pragmatismo do SRP.
  3. ( ) O SRP proíbe que uma classe como Conta gerencie seu próprio saldo internamente.
  4. ( ) A regra de equilíbrio é agrupar o que muda junto e pelos mesmos motivos.
DicaAssociação: Dependência (“Usa um”)
  1. ( ) É o vínculo mais fraco entre dois objetos.
  2. ( ) O objeto usado numa dependência costuma ser guardado como atributo de longo prazo.
  3. ( ) É análoga a usar uma caneta emprestada do balcão e devolvê-la.
  4. ( ) Carrinho.confereTotal(ArrayList<Produto> itens) recebendo a lista como parâmetro é um exemplo de dependência.
DicaAssociação: Agregação (“Tem um”)
  1. ( ) É uma relação todo-parte em que a parte pode existir independentemente da destruição do todo.
  2. ( ) Se um Carrinho for destruído, os Produto que ele referenciava também são destruídos.
  3. ( ) Vários objetos “todo” diferentes podem referenciar o mesmo objeto “parte” simultaneamente.
  4. ( ) É uma forma de posse mais fraca do que a Composição.
DicaAssociação: Composição (“É parte de”)
  1. ( ) É a forma mais forte de associação, com posse exclusiva do todo sobre a parte.
  2. ( ) A parte não tem sentido lógico de existir fora do todo — nasce e morre com ele.
  3. ( ) Um Documento e suas Pagina são um exemplo clássico de Composição.
  4. ( ) A escolha entre Agregação e Composição é sempre puramente sintática, nunca uma decisão de negócio.
DicaDelegação
  1. ( ) Ocorre quando um objeto orquestrador repassa o trabalho a um objeto especialista associado.
  2. ( ) joao.getCartao().processar(500.0) é um exemplo correto de delegação.
  3. ( ) joao.pagar(500.0), delegando internamente ao Cartao, corrige a violação de Demeter do exemplo anterior.
  4. ( ) A delegação é o mecanismo que torna a composição uma alternativa flexível à herança.
DicaTell, Don’t Ask e a Síntese da Aula
  1. ( ) “Tell, Don’t Ask” propõe emitir comandos aos objetos, em vez de sondar seu estado interno para decidir por fora.
  2. ( ) Associação, SRP e Delegação são princípios independentes, sem nenhuma relação entre si.
  3. ( ) Um sistema bem decomposto reduz a carga cognitiva ao permitir raciocinar sobre uma responsabilidade por vez.
  4. ( ) Delegar ao especialista certo permite trocar sua implementação sem que o orquestrador precise mudar.