Exercícios — Herança: DNA, Fragilidade e Template Method

Aula 8 — Programação Orientada a Objetos

Autor

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

Aula Soluções

Questões discursivas

  1. Explique o Problema da Classe Base Frágil usando o exemplo de processarLote/processarPagamento. Por que o bug de contagem dupla surge sem que nenhum código tenha sido escrito “errado” isoladamente?

  2. Um desenvolvedor faz GerenciadorDeCobrancas extends ListaDeContatos só para reaproveitar métodos de manipulação de array. Explique por que isso é um erro de design, e como a composição resolveria o mesmo problema sem os riscos.

  3. Explique o padrão Template Method usando o exemplo de realizarPagamento. Por que o método principal precisa ser final, e o que aconteceria se cada subclasse pudesse sobrescrevê-lo livremente?

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 — Herança como Identidade (“É-UM”)
  • □ A herança estabelece uma relação de identidade mais profunda que a associação — a relação “É-UM”.
  • □ O filho, ao herdar, assina apenas o contrato do pai, sem incorporar sua estrutura interna.
  • □ Num sistema onde Cartao herda de MeioPagamentoBase, e Cartao também guarda uma referência a um objeto Endereco como atributo, o acoplamento entre Cartao e Endereco é tão forte quanto o acoplamento entre Cartao e MeioPagamentoBase.
  • □ Herança de atributos significa que o filho fisicamente possui, na memória, os campos definidos no pai.
NotaTeste 2 — O Modificador protected
  • protected permite que subclasses manipulem o estado herdado sem expô-lo ao resto do sistema.
  • protected é equivalente, em termos de encapsulamento, a private.
  • □ Se MeioPagamentoBase alterar de protected para private o atributo idCobranca, todo Cartao, Pix e Boleto que acessava esse atributo diretamente (sem usar um método) para de compilar.
  • □ Sem final no método de transição, uma subclasse pode ignorar a validação do pai livremente.
NotaTeste 3 — O Problema da Classe Base Frágil
  • □ Se MeioPagamentoBase.processarLote() for reescrito para não chamar mais processarPagamento() internamente (processando os valores diretamente, sem delegar), o Cartao que sobrescreve ambos os métodos passará a contar corretamente, sem nenhuma mudança no código do Cartao.
  • □ O bug de contagem dupla em processarLote surge porque o polimorfismo desvia a chamada interna para o método sobrescrito do filho.
  • □ Esse tipo de erro costuma ser detectado pelo compilador antes da execução.
  • □ Invariantes que vivem apenas na lógica implícita do pai são as mais perigosas de se violar sem perceber.
NotaTeste 4 — Herança por Conveniência vs. Especialização Legítima
  • □ Uma classe RelatorioFinanceiro que precisa apenas do método formatarMoeda() de uma classe utilitária FormatadorNumerico deveria herdar de FormatadorNumerico para ganhar acesso a esse método.
  • GerenciadorDeCobrancas extends ListaDeContatos é um exemplo de especialização legítima.
  • □ Uma classe RegistroDeAuditoria que estende MeioPagamentoBase só para reaproveitar o atributo idCobranca, mas nunca é armazenada numa List<Pagavel> nem passada onde se espera um Pagavel, ainda é uma especialização legítima, desde que implemente corretamente criarCobranca().
  • □ A composição é a alternativa correta quando a relação real é “usa um”, não “é um”.
NotaTeste 5 — Invariantes Invisíveis
  • □ Se MeioPagamentoBase documentar explicitamente, em um comentário Javadoc, que validar() deve sempre rodar antes de processar(), essa dependência de ordem deixa de ser uma invariante invisível.
  • □ Se o pai espera que um método nunca retorne null, uma subclasse pode alterar essa semântica livremente sem risco.
  • □ Se Cartao.alterarStatus() sobrescrever o método do pai e esquecer de chamar super.alterarStatus(), mas ainda assim atualizar corretamente o campo status com sua própria lógica, o objeto nunca fica num estado inconsistente.
  • □ Adicionar a anotação @Deprecated a um método do pai é suficiente para transformar uma invariante antes invisível (como um pressuposto de ordem) numa restrição verificada pelo compilador.
NotaTeste 6 — A Base como Guardiã
  • □ Atributos que regem invariantes críticos devem ser private na classe base, não protected.
  • □ Métodos final impedem que subclasses sobrescrevam e subvertam a validação da base.
  • □ Métodos-gancho (protected abstract) delegam ao filho só o detalhe técnico, sem expor o fluxo inteiro.
  • □ Blindar a base dessa forma torna a integridade do sistema dependente do acerto individual de cada subclasse.
NotaTeste 7 — Classes Abstratas
  • □ Uma classe abstrata pode ter métodos concretos, resolvendo parte do comportamento do objeto.
  • □ O compilador impede a instanciação direta de uma classe abstrata via new.
  • □ Uma classe abstrata é uma limitação técnica sem nenhum propósito arquitetural além de “não poder instanciar”.
  • □ O método abstract de uma classe abstrata representa a parte “sem motor” da máquina semi-acabada.
NotaTeste 8 — Herança de Estado vs. Comportamento
  • □ Uma classe Pix que implementa Pagavel (mas não estende nenhuma classe) e nunca armazena idCobranca como atributo, gerando-o sob demanda dentro do próprio método getIdCobranca(), ainda cumpre corretamente o contrato da interface.
  • □ Se MeioPagamentoBase tiver um atributo logsInternos que o Pix nunca usa, é possível, em Java, fazer com que instâncias de Pix simplesmente não aloquem espaço para esse campo na Heap.
  • □ Existe uma sintaxe padrão em Java para um filho “recusar” um atributo herdado que ele não usa.
  • □ Mudar o tipo de um atributo herdado (de String para UUID, por exemplo) pode exigir revisar todas as subclasses.
NotaTeste 9 — Template Method: o Esqueleto
  • □ O método principal do Template Method costuma ser marcado como final para travar a ordem dos passos.
  • □ Se MeioPagamentoBase tivesse dois métodos-gancho abstratos (criarCobrancaEspecifica() e validarRegrasEspecificas()), uma subclasse poderia implementar apenas um dos dois e ainda assim compilar normalmente.
  • □ Se cada subclasse pudesse sobrescrever o método principal livremente, a ordem de validação e log deixaria de ser garantida.
  • □ O Template Method reduz a previsibilidade do sistema, pois cada filho decide sua própria sequência de passos.
NotaTeste 10 — Inversão de Controle e o Princípio de Hollywood
  • □ Se um framework de testes chama automaticamente o método setUp() de uma classe de teste antes de cada teste, sem que o desenvolvedor precise chamá-lo manualmente, esse framework está aplicando o mesmo Princípio de Hollywood do Template Method.
  • □ Um script procedural que lê um arquivo, chama uma função de parsing de uma biblioteca, e depois decide o que fazer com o resultado, já está aplicando Inversão de Controle, porque delega parte do trabalho para código de terceiros (a biblioteca).
  • □ No Template Method, é o filho quem decide quando delegar a execução de volta para o pai.
  • □ Um Template Method com um único método-gancho, mas que impõe quinze passos obrigatórios e inalteráveis entre a validação e a chamada do gancho, seria um exemplo do risco de “camisa de força” mencionado na aula, mesmo respeitando a sintaxe do padrão corretamente.
NotaTeste 11 — Vantagens e Riscos do Template Method
  • □ Se uma nova subclasse Cripto for adicionada ao sistema um ano depois da aula, sem que o desenvolvedor releia a documentação do Template Method, o passo de registrarLog() ainda vai rodar corretamente para ela.
  • □ Se Pix, Boleto e Cartao cada um sobrescrevesse seu próprio método realizarPagamento() completo (em vez de usar o esqueleto da base), mudar a regra de log exigiria editar três lugares em vez de um.
  • □ Exige que o desenvolvedor de cada subclasse conheça o fluxo inteiro do algoritmo, não só seu gancho.
  • □ É um mecanismo de Inversão de Controle: a base chama o filho, não o contrário.
NotaTeste 12 — Síntese: Herança como Ferramenta de Especialização
  • □ Herança bem usada exige DNA compartilhado real, não apenas o desejo de reaproveitar código.
  • □ Uma subclasse PixPremium que estende Pix apenas para renomear o método criarCobranca() para criarCobrancaPremium(), sem adicionar nenhum campo ou comportamento novo, e mantendo o mesmo contrato, é uma especialização legítima porque ainda compartilha o DNA do pai.
  • □ Blindar a base (atributos private, métodos final, ganchos abstract) reduz o risco de Classe Base Frágil.
  • □ O acoplamento gerado pela herança é, em geral, mais fraco do que o gerado por composição simples.