Decomposição e Responsabilidade
Aula 4 — Programação Orientada a Objetos
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:
- Como reconhecer, no próprio código, uma classe que “sabe demais” ou “faz demais”?
- O que exatamente medem coesão e acoplamento, e por que quase sempre andam juntos?
- Quando a solução — dividir responsabilidades — vira ela mesma um problema?
- 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:
- Fragilidade extrema: se a regra de imposto muda, altera-se
Pedido; se o banco muda de MySQL para MongoDB, altera-sePedido; se o layout do recibo muda, altera-sePedido. Cada alteração arrisca introduzir bugs em partes não relacionadas. - Acoplamento rígido com infraestrutura:
Pedidoestá “sujo” com SQL e SMTP — impossível testar o cálculo do total sem um banco de dados ativo. - 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). - Carga cognitiva alta: entender o que um
Pedidofaz 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
Usuariovalida a senha E salva os dados no banco.” - “A classe
Relatorioformata os dados em PDF E envia por FTP.” - “A classe
Pedidocalcula 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:
- Testabilidade: um teste pode injetar um
PagavelMocke umEmailMock— a lógica definalizar()é verificada sem cobrar cartão de verdade nem enviar spam. - Reuso:
ServicoEmailpode ser usado por outras classes (RecuperarSenha, por exemplo), já que não está mais preso dentro dePedido. - Manutenibilidade: um bug no pagamento fica isolado nas classes de pagamento, sem risco de quebrar o envio de e-mails.
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).
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.
joao.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:
- Reconhecer uma Classe Deus: ela precisa de “E” para ser descrita numa frase, ou mistura razões de mudança de atores diferentes.
- 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.
- 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).
- 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
A classe
Pedidodo 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.Um colega decide aplicar o SRP “ao pé da letra” e separa a classe
Pedidoem 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.Explique a diferença entre Agregação e Composição usando o exemplo de
ClienteeCartao. 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).
- ( ) É identificada por centralizar lógicas de múltiplos atores (financeiro, infraestrutura, UI) num único arquivo.
- ( ) O “Teste do E” ajuda a diagnosticá-la: se a classe faz X E Y, provavelmente tem responsabilidades demais.
- ( ) Uma Classe Deus facilita a manutenção, pois toda a lógica fica concentrada num só lugar.
- ( ) Testar isoladamente uma responsabilidade de uma Classe Deus costuma exigir efeitos colaterais indesejados (e-mail, banco).
- ( ) Coesão mede o quão focadas e inter-relacionadas são as responsabilidades internas de uma classe.
- ( ) O objetivo de um bom design é alto acoplamento e baixa coesão.
- ( ) Uma classe de baixa coesão tenta ser várias coisas ao mesmo tempo.
- ( ) Aumentar a coesão de uma classe costuma facilitar a redução do acoplamento do sistema.
- ( ) O SRP afirma que uma classe deve ter uma, e apenas uma, razão para mudar.
- ( ) Se a descrição de uma classe exige a conjunção “E”, ela provavelmente viola o SRP.
- ( ) Aplicar o SRP significa que uma classe só pode ter um único método público.
- ( )
Pedidoque calcula total, processa pagamento e envia e-mail no mesmo método viola o SRP.
- ( ) Ocorre quando um objeto recebe seus colaboradores de fora, em vez de criá-los internamente com
new. - ( ) A injeção via construtor garante que o objeto nunca exista sem suas dependências essenciais.
- ( ) A DI aumenta o acoplamento, pois obriga o objeto a conhecer quem o instanciou.
- ( ) Sem DI, testar
Pedido.finalizar()isoladamente, sem efeitos colaterais reais, é muito mais difícil.
- ( ) Segundo Robert Martin, um módulo deve ser responsável perante um, e apenas um, ator.
- ( ) Misturar lógica solicitada pelo RH e pelo Financeiro na mesma classe é seguro, pois são setores diferentes.
- ( ) O conflito de atores é uma fonte comum de “quebras colaterais” em classes com múltiplas razões de mudança.
- ( ) A verdadeira fonte de mudança em software costuma ser as pessoas ou departamentos que a solicitam.
- ( ) LCOM avalia a falta de coesão observando se os métodos de uma classe compartilham os mesmos atributos.
- ( ) Um LCOM alto indica uma classe muito coesa e bem projetada.
- ( ) Se metade dos métodos usa o atributo A e a outra metade usa só o atributo B, a classe deveria ser dividida.
- ( ) LCOM é uma forma de provar matematicamente que uma classe é, na prática, duas classes disfarçadas.
- ( ) A Cirurgia de Espingarda ocorre quando uma única mudança de negócio exige tocar em muitas classes minúsculas.
- ( ) O Modelo Anêmico é uma consequência possível da aplicação fanática e sem pragmatismo do SRP.
- ( ) O SRP proíbe que uma classe como
Contagerencie seu próprio saldo internamente. - ( ) A regra de equilíbrio é agrupar o que muda junto e pelos mesmos motivos.
- ( ) É o vínculo mais fraco entre dois objetos.
- ( ) O objeto usado numa dependência costuma ser guardado como atributo de longo prazo.
- ( ) É análoga a usar uma caneta emprestada do balcão e devolvê-la.
- ( )
Carrinho.confereTotal(ArrayList<Produto> itens)recebendo a lista como parâmetro é um exemplo de dependência.
- ( ) É uma relação todo-parte em que a parte pode existir independentemente da destruição do todo.
- ( ) Se um
Carrinhofor destruído, osProdutoque ele referenciava também são destruídos. - ( ) Vários objetos “todo” diferentes podem referenciar o mesmo objeto “parte” simultaneamente.
- ( ) É uma forma de posse mais fraca do que a Composição.
- ( ) É a forma mais forte de associação, com posse exclusiva do todo sobre a parte.
- ( ) A parte não tem sentido lógico de existir fora do todo — nasce e morre com ele.
- ( ) Um
Documentoe suasPaginasão um exemplo clássico de Composição. - ( ) A escolha entre Agregação e Composição é sempre puramente sintática, nunca uma decisão de negócio.
- ( ) Ocorre quando um objeto orquestrador repassa o trabalho a um objeto especialista associado.
- ( )
joao.getCartao().processar(500.0)é um exemplo correto de delegação. - ( )
joao.pagar(500.0), delegando internamente aoCartao, corrige a violação de Demeter do exemplo anterior. - ( ) A delegação é o mecanismo que torna a composição uma alternativa flexível à herança.
- ( ) “Tell, Don’t Ask” propõe emitir comandos aos objetos, em vez de sondar seu estado interno para decidir por fora.
- ( ) Associação, SRP e Delegação são princípios independentes, sem nenhuma relação entre si.
- ( ) Um sistema bem decomposto reduz a carga cognitiva ao permitir raciocinar sobre uma responsabilidade por vez.
- ( ) Delegar ao especialista certo permite trocar sua implementação sem que o orquestrador precise mudar.