Exercícios — Polimorfismo, Binding e Generics

Aula 7 — Programação Orientada a Objetos

Autor

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

Aula Soluções

Questões discursivas

  1. Defina Static (Early) Binding e Dynamic (Late) Binding, explicitando em qual momento do ciclo de vida do software cada um atua e qual agente (compilador ou JVM) toma a decisão. Por que o desacoplamento em Orientação a Objetos depende criticamente do Late Binding?

  2. Um desenvolvedor júnior escreve um método efetuarCobranca que inspeciona uma String tipoPagamento numa cadeia de if/else para disparar PixService ou BoletoService. Critique esse design com base no Princípio Aberto/Fechado e mostre, em texto, como o Polimorfismo de Inclusão elimina esse sintoma.

  3. Explique o cenário de “Heterogeneidade Acidental” numa lista sem Generics num sistema de checkout, e como List<Pagavel> protege o usuário final de um ClassCastException em produção.

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 — O que é Polimorfismo
  • □ Duas classes que implementam métodos com o mesmo nome, mas sem relação de herança ou interface comum entre elas, já caracterizam polimorfismo, pois cada uma se comporta de forma diferente.
  • □ O objetivo prático do polimorfismo é reduzir o acoplamento e aumentar a extensibilidade do sistema.
  • pagamentoConfirmado() pode se comportar como consulta de API no Pix e como verificação de data no Boleto.
  • □ O código que chama a operação precisa conhecer os detalhes de implementação de quem a executa.
NotaTeste 2 — Late Binding (Dynamic Dispatch)
  • □ Se a interface Pagavel não declarasse criarCobranca(), mas todas as classes que a implementam (Pix, Cartao) declarassem esse método por conta própria, a chamada forma.criarCobranca(total) ainda compilaria normalmente.
  • □ Num sistema de plugins em que cada plugin implementa a interface comum Processador, é possível adicionar um plugin novo, compilado separadamente, sem recompilar o núcleo do sistema — graças ao mesmo mecanismo que decide, em Pagavel, qual criarCobranca() roda.
  • □ A JVM decide olhando o tipo declarado da variável, não o objeto real na memória.
  • □ Late Binding é o que permite trocar a implementação injetada sem que o código cliente perceba.
NotaTeste 3 — Polimorfismo Universal
  • □ Uma nova classe que implemente Pagavel depois que o sistema já estiver em produção pode ser usada em qualquer lugar que espere um Pagavel, sem exigir recompilação do código cliente existente.
  • □ O Polimorfismo Paramétrico exige que todos os tipos usados pertençam à mesma hierarquia de herança.
  • □ Um método genérico ordenar(List<T> lista) que funciona tanto para List<Produto> quanto para List<Cliente> deixaria de compilar se Produto e Cliente não implementassem uma interface Comparable em comum.
  • □ Um sistema que usa apenas Polimorfismo Ad-hoc (sobrecarga e coerção) já suporta, em princípio, um número infinito de tipos diferentes sem precisar editar código existente.
NotaTeste 4 — Polimorfismo Ad-hoc
  • □ Um método enviar(String email) e um método enviar(int codigo) na mesma classe Notificador configuram sobrecarga, mesmo que um deles nunca seja chamado em nenhum lugar do sistema.
  • □ A sobrecarga é considerada um polimorfismo verdadeiramente dinâmico e extensível.
  • □ Se dois métodos sobrecarregados enviar(String email) e enviar(Object dado) puderem ambos aceitar um argumento do tipo String, o compilador escolhe sempre a versão mais genérica (Object), para reduzir o número de conversões implícitas.
  • □ A coerção de int para double numa expressão aritmética pode ser desativada em tempo de execução dependendo do valor concreto da variável, da mesma forma que o Late Binding decide em runtime qual método roda.
NotaTeste 5 — Binding Estático vs. Dinâmico
  • □ Um IDE que sugere automaticamente, enquanto o código é digitado, qual método enviar() sobrecarregado será chamado, está inspecionando o comportamento do Static Binding, não do Dynamic Binding.
  • □ Métodos static e private em Java são resolvidos via Dynamic Binding.
  • □ O Dynamic Binding é o que sustenta o desacoplamento e a extensibilidade típicos da Orientação a Objetos.
  • □ A Injeção de Dependência se apoia no Dynamic Binding para trocar implementações sem alterar o código cliente.
NotaTeste 6 — O Fim do “Mar de IFs”
  • □ Uma cadeia de if/else que verifica, dentro de um método de log genérico, se o parâmetro é null antes de gravá-lo, é um sintoma da mesma violação de OCP que a cadeia de if/else sobre tipos de pagamento.
  • □ Com polimorfismo, adicionar um novo meio de pagamento não exige alterar o código do controlador.
  • □ O uso de instanceof para decidir o comportamento com base no tipo concreto é incentivado pelo polimorfismo.
  • □ Delegar a decisão para o próprio objeto substitui a necessidade de perguntar “qual é o seu tipo?”.
NotaTeste 7 — O Princípio Aberto/Fechado (OCP)
  • □ Um sistema está “aberto para extensão” quando comportamentos novos entram via novas classes, não via edição das antigas.
  • □ Estar “fechado para modificação” significa que o código cliente não precisa ser retestado ao aceitar um novo ator.
  • □ Programar para interfaces, em vez de classes concretas, é o mecanismo que viabiliza o cumprimento do OCP.
  • □ O OCP recomenda revisar e reescrever o código cliente a cada novo comportamento adicionado ao sistema.
NotaTeste 8 — Substitutibilidade
  • □ A substitutibilidade é a capacidade de trocar implementações concretas por uma abstração comum sem impacto perceptível.
  • CheckoutController trata Pix, Boleto e Cartao de forma uniforme através da “lente” do tipo Pagavel.
  • □ Para usar criarCobranca(), o controlador precisa saber qual classe concreta está por trás da referência.
  • □ A substitutibilidade é o que permite ao sistema aceitar novos meios de pagamento sem alterar o núcleo.
NotaTeste 9 — O Problema dos Raw Types
  • □ Sem Generics, uma coleção Java podia armazenar referências genéricas do tipo Object.
  • □ O risco de inserir um objeto incompatível numa lista sem Generics é detectado imediatamente pelo compilador.
  • □ Recuperar dados de uma coleção não parametrizada exigia casting manual, sujeito a ClassCastException.
  • □ A detecção tardia de um erro de tipo é o principal risco prático dos raw types.
NotaTeste 10 — Generics como Etiqueta de Segurança
  • List<Pagavel> desloca a detecção de erros de tipo do tempo de execução para o tempo de compilação.
  • □ Generics elimina a necessidade de casting manual na leitura de itens de uma coleção parametrizada.
  • □ Generics reduz a legibilidade do código, pois exige mais caracteres na declaração de tipos.
  • □ Proibir a inserção de tipos incompatíveis é uma das formas como Generics aumenta a robustez do sistema.
NotaTeste 11 — Polimorfismo Paramétrico e Identidade
  • □ O Polimorfismo Paramétrico usa variáveis de tipo (como <T>) mantendo a segurança de tipos estáticos.
  • □ Diferente do Polimorfismo de Inclusão, o Paramétrico não exige que os tipos usados pertençam à mesma árvore de herança.
  • □ Um Repositorio<T> genérico centraliza a lógica de infraestrutura sem duplicar código para cada entidade.
  • □ Recuperar um item de um Repositorio<T> obriga o desenvolvedor a fazer um cast manual para o tipo concreto.
NotaTeste 12 — Síntese: Contrato, Polimorfismo e Segurança de Tipo
  • □ Interfaces definem o contrato; Polimorfismo de Inclusão o torna substituível; Generics protege coleções desses contratos.
  • □ O uso conjunto de interfaces, polimorfismo e Generics é o que sustenta sistemas verdadeiramente Plug-and-Play.
  • □ Um sistema bem desenhado com essas três ferramentas ainda pode exigir ifs de tipo em pontos centrais do fluxo.
  • □ A meta comum às três ferramentas é mover decisões e erros para o momento mais cedo e mais seguro possível do ciclo de vida do software.