Aula Soluções
Questões discursivas
Um método sacar(double valor) em ContaBancaria não impõe restrição de horário. Uma subclasse ContaInvestimento sobrescreve esse método lançando uma RuntimeException caso o saque ocorra fora do horário comercial — uma regra que não existe na base. Explique, em termos das regras de contrato do LSP, exatamente qual regra é violada e por quê.
Explique a diferença entre Early Binding e Late Binding usando o protocolo de despacho da VTable (os quatro passos vistos na aula). Em qual desses passos a JVM realmente decide qual código executar, e por que essa decisão não poderia ser tomada só com a informação disponível ao compilador?
Um desenvolvedor precisa decidir se uma falha de comunicação com um gateway de pagamento externo deve ser modelada como uma exceção Checked ou Unchecked. Argumente a favor de uma das duas escolhas, e explique por que a escolha errada geraria um problema de design (não só um erro de sintaxe).
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).
- □ Se um método for marcado como
static, a JVM ainda assim consulta a VTable da classe real do objeto para decidir qual código executar.
- □ Em um sistema que usasse Early Binding em vez de Late Binding, adicionar uma nova subclasse de
Pagavel exigiria recompilar o código que já chamava métodos de Pagavel.
- □ A VTable consultada numa chamada
p.pagamentoConfirmado(id) é determinada pela classe declarada da variável p, não pela classe do objeto apontado por ela.
- □ Se duas classes não relacionadas por herança ou interface comum implementarem um método com a mesma assinatura, a JVM ainda assim pode fazer Late Binding entre elas ao chamar esse método através de uma referência comum.
- □ Se um método sobrescrito muda a semântica do método original (por exemplo, de “validar” para “excluir o registro”), o compilador não impede essa mudança, mesmo com
@Override presente.
- □ Se a classe pai remover o método que uma subclasse originalmente sobrescrevia (renomeando-o), o compilador acusa erro na subclasse graças à anotação
@Override.
- □ Um método sobrescrito que devolve um subtipo mais específico do tipo de retorno original (covariância de retorno) ainda é uma sobrescrita válida.
- □
@Override transforma automaticamente qualquer alteração de comportamento do método em uma violação de compilação, independente da assinatura.
- □ Se \(\phi(x)\) representa que “o método nunca retorna null”, e existe um subtipo \(S\) de \(T\) cujas instâncias podem retornar null nesse mesmo método, a fórmula \(\forall x{:}T,\phi(x) \implies \forall y{:}S,\phi(y)\) é violada por esse subtipo.
- □ O LSP é satisfeito sempre que o subtipo compila corretamente contra a interface do tipo base, independentemente do comportamento em tempo de execução.
- □ Um sistema de testes automatizados que roda a mesma bateria de testes contra
Pagavel, trocando só a implementação concreta injetada, está aplicando na prática a intuição do “cliente cego” usada para explicar o LSP.
- □ Se um subtipo \(S\) satisfaz o LSP em relação a \(T\), isso implica necessariamente que \(T\) também satisfaz o LSP em relação a \(S\).
- □ Se a base aceita valores de 0 a 1000, e uma subclasse aceita valores de -1000 a 2000 (um domínio maior), essa subclasse respeita a regra de pré-condições do LSP.
- □ Uma subclasse pode enfraquecer uma pré-condição do pai (aceitar mais entradas do que ele aceitava) sem violar o LSP.
- □ Se um método do pai não documenta explicitamente nenhuma pré-condição, uma subclasse está livre para adicionar qualquer restrição de entrada sem risco de violar o LSP.
- □ Fortalecer uma pré-condição é equivalente, na prática, a remover funcionalidade que o cliente já esperava ter disponível através do tipo base.
- □ Se o pai promete retornar um objeto nunca nulo, e o filho retorna um objeto “vazio” (um objeto real, mas sem dado útil) em vez de null, isso ainda pode violar a regra de pós-condições dependendo do que o cliente faz com o resultado.
- □ Uma subclasse que sempre retorna um resultado mais específico e mais completo do que o pai prometia está fortalecendo a pós-condição, o que é permitido pelo LSP.
- □ Uma subclasse que herda um atributo
protected responsável por uma invariante crítica pode alterar esse atributo livremente, desde que não sobrescreva nenhum método do pai.
- □ Preservar uma invariante herdada significa apenas que o valor do atributo nunca muda depois da construção do objeto.
- □ O paradoxo Círculo-Elipse só aparece porque
Elipse expõe um método mutável (setAxes) que permite alterar os eixos de forma independente depois da criação do objeto.
- □ Se
Circulo e Elipse fossem ambos imutáveis (sem nenhum setAxes, só construtores), a herança entre eles deixaria de gerar a mesma violação de LSP.
- □ O paradoxo prova que herança geométrica sempre deve ser evitada em qualquer sistema orientado a objetos, mesmo quando os objetos são imutáveis.
- □ Substituir a herança
Circulo extends Elipse por uma interface comum FormaGeometrica, sem o método setAxes, é uma forma válida de evitar o paradoxo.
- □ Uma subclasse que lança uma exceção verificada (
checked) não declarada pelo método do pai obriga o código cliente já existente a ser recompilado ou reescrito para tratar essa nova exceção.
- □ Lançar
UnsupportedOperationException para “pular” um método herdado que a subclasse não quis implementar é uma prática recomendada quando a subclasse é uma especialização legítima.
- □ Se
Pagavel.criarCobranca() não declara nenhuma exceção, uma subclasse que lança uma RuntimeException inesperada em condições normais de uso ainda pode violar o espírito do LSP, mesmo sem quebrar a compilação.
- □ Um subtipo que lança um subtipo mais específico de uma exceção já esperada pelo pai (por exemplo,
SaldoInsuficienteException extends PagamentoException, quando o pai já declarava lançar PagamentoException) respeita o contrato de exceção do LSP.
- □ Organizar exceções como uma hierarquia de tipos (em vez de uma lista de mensagens de texto) permite que o mesmo bloco
catch trate corretamente subtipos de exceção criados depois que aquele código já foi escrito.
- □ Se um bloco
catch (PagamentoException e) vier antes de um bloco catch (CartaoExpiradoException e) no mesmo try, sendo CartaoExpiradoException subtipo de PagamentoException, o código não compila.
- □ Capturar
Exception de forma genérica no nível mais alto do sistema é sempre uma boa prática, pois garante que nenhum erro, de nenhum tipo, escape sem tratamento.
- □ Tratar o erro como uma transição de estado prevista (por exemplo, de “Processando” para “Falha de Pagamento”) é incompatível com o uso de uma hierarquia de exceções para organizar essas falhas.
- □ Se um método interno de baixo nível lança
SQLException e essa exceção se propaga sem tradução até um controlador de alto nível, o CheckoutController passa a depender de um detalhe de implementação (o uso de SQL) que deveria estar oculto.
- □ Reembalar uma exceção técnica numa exceção de domínio, descartando a exceção original em vez de passá-la como causa, preserva completamente a capacidade de depuração técnica do erro.
- □ Um valor de entrada inválido detectado logo no início de um método é um exemplo típico de erro que deve ser tratado com uma exceção Unchecked, seguindo o princípio Fail-Fast.
- □ Capturar uma
IllegalArgumentException lançada por uma validação de contrato, só para permitir que a execução continue silenciosamente com um valor padrão, resolve o problema de origem que gerou a exceção.