Exercícios — Herança: DNA, Fragilidade e Template Method
Aula 8 — Programação Orientada a Objetos
Marcos M. Raimundo — Instituto de Computação, UNICAMP
Questões discursivas
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?Um desenvolvedor faz
GerenciadorDeCobrancas extends ListaDeContatossó 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.Explique o padrão Template Method usando o exemplo de
realizarPagamento. Por que o método principal precisa serfinal, 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).
- □ 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
Cartaoherda deMeioPagamentoBase, eCartaotambém guarda uma referência a um objetoEnderecocomo atributo, o acoplamento entreCartaoeEnderecoé tão forte quanto o acoplamento entreCartaoeMeioPagamentoBase. - □ Herança de atributos significa que o filho fisicamente possui, na memória, os campos definidos no pai.
protected
- □
protectedpermite que subclasses manipulem o estado herdado sem expô-lo ao resto do sistema. - □
protectedé equivalente, em termos de encapsulamento, aprivate. - □ Se
MeioPagamentoBasealterar deprotectedparaprivateo atributoidCobranca, todoCartao,PixeBoletoque acessava esse atributo diretamente (sem usar um método) para de compilar. - □ Sem
finalno método de transição, uma subclasse pode ignorar a validação do pai livremente.
- □ Se
MeioPagamentoBase.processarLote()for reescrito para não chamar maisprocessarPagamento()internamente (processando os valores diretamente, sem delegar), oCartaoque sobrescreve ambos os métodos passará a contar corretamente, sem nenhuma mudança no código doCartao. - □ O bug de contagem dupla em
processarLotesurge 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.
- □ Uma classe
RelatorioFinanceiroque precisa apenas do métodoformatarMoeda()de uma classe utilitáriaFormatadorNumericodeveria herdar deFormatadorNumericopara ganhar acesso a esse método. - □
GerenciadorDeCobrancas extends ListaDeContatosé um exemplo de especialização legítima. - □ Uma classe
RegistroDeAuditoriaque estendeMeioPagamentoBasesó para reaproveitar o atributoidCobranca, mas nunca é armazenada numaList<Pagavel>nem passada onde se espera umPagavel, ainda é uma especialização legítima, desde que implemente corretamentecriarCobranca(). - □ A composição é a alternativa correta quando a relação real é “usa um”, não “é um”.
- □ Se
MeioPagamentoBasedocumentar explicitamente, em um comentário Javadoc, quevalidar()deve sempre rodar antes deprocessar(), 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 chamarsuper.alterarStatus(), mas ainda assim atualizar corretamente o campostatuscom sua própria lógica, o objeto nunca fica num estado inconsistente. - □ Adicionar a anotação
@Deprecateda um método do pai é suficiente para transformar uma invariante antes invisível (como um pressuposto de ordem) numa restrição verificada pelo compilador.
- □ Atributos que regem invariantes críticos devem ser
privatena classe base, nãoprotected. - □ Métodos
finalimpedem 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.
- □ 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
abstractde uma classe abstrata representa a parte “sem motor” da máquina semi-acabada.
- □ Uma classe
Pixque implementaPagavel(mas não estende nenhuma classe) e nunca armazenaidCobrancacomo atributo, gerando-o sob demanda dentro do próprio métodogetIdCobranca(), ainda cumpre corretamente o contrato da interface. - □ Se
MeioPagamentoBasetiver um atributologsInternosque oPixnunca usa, é possível, em Java, fazer com que instâncias dePixsimplesmente 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
StringparaUUID, por exemplo) pode exigir revisar todas as subclasses.
- □ O método principal do Template Method costuma ser marcado como
finalpara travar a ordem dos passos. - □ Se
MeioPagamentoBasetivesse dois métodos-gancho abstratos (criarCobrancaEspecifica()evalidarRegrasEspecificas()), 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.
- □ 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.
- □ Se uma nova subclasse
Criptofor adicionada ao sistema um ano depois da aula, sem que o desenvolvedor releia a documentação do Template Method, o passo deregistrarLog()ainda vai rodar corretamente para ela. - □ Se
Pix,BoletoeCartaocada um sobrescrevesse seu próprio métodorealizarPagamento()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.
- □ Herança bem usada exige DNA compartilhado real, não apenas o desejo de reaproveitar código.
- □ Uma subclasse
PixPremiumque estendePixapenas para renomear o métodocriarCobranca()paracriarCobrancaPremium(), 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étodosfinal, ganchosabstract) 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.