Exercícios — Composição de Sistemas: Contratos e Estabilidade

Aula 3 — Programação Orientada a Objetos

Autor

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

Aula Soluções

Questões discursivas

  1. Explique a metáfora “Fabricante de Peças vs. Arquiteto de Sistemas”. Por que a preocupação puramente anatômica (sintaxe, this, private) é insuficiente para construir sistemas sustentáveis a longo prazo?

  2. O código pedido.getCliente().getEndereco().getCEP() é um “Naufrágio de Código” (train wreck). Explique tecnicamente por que essa linha representa um risco arquitetural, e como um método de delegação resolveria o problema.

  3. Explique como a interface atua como um “escudo” para quem usa um objeto, usando a analogia do controle remoto ou do padrão USB-C para justificar por que não é preciso conhecer a “fiação interna” de um colaborador.

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).

NotaTeste 1 — Do Fabricante ao Arquiteto
  • □ Se um projetista dedicasse todo o cuidado de design a otimizar cada classe isoladamente — sem nunca considerar como as mensagens fluem entre elas — o sistema resultante seria tão sustentável quanto um projetado com atenção à rede de colaboradores.
  • □ Um sistema robusto pode ser visto como um amontoado de classes que expõem seus dados livremente.
  • □ Projetar por “encaixes” (interfaces) permite ignorar o “como” interno de cada peça no rascunho do sistema.
  • □ A maturidade de design é atingida ao definir as mensagens entre objetos antes de escrever a lógica interna dos métodos.
NotaTeste 2 — Colaboração e Baixo Acoplamento
  • □ Um objeto especialista deve, preferencialmente, não conhecer a existência de seu orquestrador.
  • ItemCarrinho.subtotal() deve acessar diretamente o atributo precoBase de Produto.
  • Carrinho.calcularTotal() usa delegação, sem conhecer a lógica interna de ItemCarrinho.
  • □ Dividir responsabilidades entre especialistas evita a criação de “Objetos Deus”.
NotaTeste 3 — Interface como Contrato
  • □ Se um método público de ValidadorFinanceiro passar a ter bugs ocasionais depois de uma refatoração puramente interna, isso já é evidência de que a separação entre interface e implementação falhou naquele ponto.
  • □ Alterar a implementação de um método, mantendo sua assinatura, não deveria exigir recompilar o cliente.
  • □ O acoplamento físico — conhecer as entranhas de outra classe — é uma das maiores causas de fragilidade.
  • □ Uma interface bem desenhada deve expor o máximo possível da estrutura interna, para facilitar o reuso.
NotaTeste 4 — Design de Caixa Preta
  • □ Atributos privados garantem que a interface pública seja a única via de interação com o objeto.
  • □ Ocultar a implementação permite que regras de negócio voláteis mudem sem efeito cascata.
  • □ Proteger o estado interno é dispensável se o programador garantir que ninguém chamará os métodos errado.
  • calcularPrecoDeVenda() pode mudar de fórmula internamente sem afetar quem já chama esse método.
NotaTeste 5 — A Lei de Demeter
  • □ Se getCEPDeEntrega() fosse reescrito para retornar o próprio objeto Endereco em vez do CEP já extraído, e o chamador então fizesse pedido.getCEPDeEntrega().getCep(), isso continuaria respeitando a Lei de Demeter tanto quanto a versão atual.
  • □ O objetivo é impedir que o conhecimento sobre a hierarquia interna do sistema se espalhe sem controle.
  • □ “Falar com estranhos” (objetos obtidos navegando por outros objetos) aumenta a resiliência do código.
  • □ Um método de delegação, como getCEPDeEntrega(), é uma forma correta de respeitar a lei.
NotaTeste 6 — O Naufrágio de Código (Train Wreck)
  • □ É identificado por longas cadeias de chamadas, como a.getB().getC().getD().
  • □ O problema real é puramente estético — o excesso de pontos polui a leitura do código.
  • □ Cada nível de navegação profunda é uma fronteira de objeto invadida.
  • □ Se a estrutura intermediária (ex.: Carteira) mudar, o código que a navegou diretamente quebra.
NotaTeste 7 — Tell, Don’t Ask na Prática
  • □ Em vez de investigar o saldo do cliente para autorizar a venda, o sistema deve pedir ao Pedido que se valide.
  • □ “Perguntar” por dados para decidir por fora é uma prática que fortalece o encapsulamento.
  • □ Mover a lógica para onde os dados residem evita que classes se tornem “fofoqueiras”.
  • pedido.clientePodePagar() é um exemplo de “Tell”: o Pedido decide internamente como verificar isso.
NotaTeste 8 — Delegação e Feature Envy
  • □ Num sistema de RH que calcula bônus a partir do Departamento de um Funcionario, se o método calcularBonus() vive dentro de Funcionario mas lê this.departamento.getMultiplicador() internamente, isso já configura uma violação de delegação, pois Funcionario está “chamando” um método de outro objeto.
  • □ Um “orquestrador” como Carrinho deve calcular pessoalmente o subtotal de cada item, para garantir precisão.
  • Feature Envy é o sintoma de uma classe mais interessada nos dados de outra do que nos próprios.
  • □ A cura para Feature Envy é mover o método para a classe que de fato detém os dados necessários.
NotaTeste 9 — Programar para Abstrações
  • □ Programar para um tipo genérico (Pagavel) em vez de uma classe concreta (Boleto) aumenta a rigidez.
  • □ O uso de tipos genéricos permite que o sistema aceite novos componentes sem alterar o código já existente.
  • □ Referenciar objetos por suas interfaces é uma recomendação central para promover flexibilidade.
  • □ Um Checkout que recebe Pagavel funciona com qualquer classe futura que implemente esse contrato.
NotaTeste 10 — O Erro de Tipagem como Diagnóstico
  • □ Um erro de compilação ao passar Boleto onde se espera CartaoDeCredito é sempre um bug do compilador.
  • □ Esse erro pode revelar que o sistema exige uma identidade específica em vez de uma capacidade funcional.
  • □ A solução típica é generalizar o parâmetro para uma interface que ambas as classes implementem.
  • □ Elevar o nível de abstração do parâmetro reduz o acoplamento entre Checkout e os métodos de pagamento.
NotaTeste 11 — Custo de Mudança e o Princípio TRUE
  • □ Um sistema pode ser “Razoável” segundo o princípio TRUE mesmo que o custo de adicionar uma funcionalidade simples cresça proporcionalmente ao tamanho total do código já escrito, contanto que o código seja bem documentado.
  • □ Um código é “Razoável” quando o custo de uma mudança é proporcional ao benefício que ela traz.
  • □ O design de caixa preta aumenta o custo de mudança, pois esconde erros atrás de interfaces.
  • □ Contratos estáveis e baixo acoplamento evitam que o custo de uma nova funcionalidade cresça exponencialmente.
NotaTeste 12 — O Papel do Orquestrador
  • Carrinho atua como “maestro”: coordena especialistas em vez de fazer o trabalho de cada um.
  • □ A fronteira entre Carrinho e ItemCarrinho é respeitada quando o Carrinho soma subtotais já calculados.
  • □ Se ItemCarrinho mudar sua fórmula de subtotal, Carrinho precisa ser reescrito para acompanhar.
  • □ Um bom orquestrador permanece estável mesmo quando a lógica interna de um especialista evolui.