Aula Soluções
Questões discursivas
(Do Antipadrão ao State) Explique, com precisão técnica, por que representar o ciclo de vida de um objeto através de uma variável status (String ou int) verificada no início de cada método viola tanto o Open/Closed Principle quanto o princípio de Fail-Fast já estudado nesta disciplina. Em seguida, demonstre como a estrutura Context/State/ConcreteState do padrão State resolve os dois problemas simultaneamente, usando o Pedido discutido em aula.
(Strategy vs. State: a Distinção que Importa) Strategy e State compartilham uma topologia de classes quase idêntica (Contexto, interface, implementações concretas), mas resolvem intenções arquiteturais diferentes. Compare os dois padrões precisamente no ponto de “quem decide qual implementação está ativa, e quando”, usando como exemplo a injeção de Pagavel no Pedido (Aula 10) contra a transição de EstadoNovo para EstadoPago. Por que essa distinção é mais importante do que a semelhança estrutural entre os dois padrões?
(Object Adapter e a Fronteira com o Legado) Considerando o cenário do LogAdapter discutido em aula (um sistema de auditoria legado, incompatível, que a equipe não pode alterar), explique por que a indústria prefere o Object Adapter (composição) ao Class Adapter (herança) nesse tipo de integração. Descreva um cenário concreto em que o fornecedor da biblioteca legada lança uma nova versão com uma classe-filha, e mostre exatamente por que o Object Adapter absorve essa mudança sem exigir uma nova classe, enquanto o Class Adapter exigiria.
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).
- □ Um método que verifica
if (status.equals("PAGO")) como primeira instrução antes de executar qualquer lógica de negócio caracteriza o mesmo tipo de acoplamento temporal descrito para o antipadrão do Pedido, independentemente de o valor de status ser uma String ou um enum.
- □ Um sistema de emissão de boletos que usa um campo
int codigoSituacao (0 = emitido, 1 = pago, 2 = vencido) verificado em cinco métodos diferentes da mesma classe está estruturalmente exposto ao mesmo padrão de fragilidade de evolução descrito para o Pedido.
- □ Se um novo estado for adicionado a um sistema que usa o antipadrão de status-como-primitivo, o compilador Java emitirá um erro de compilação em todo método que não trate explicitamente esse novo estado.
- □ Testar isoladamente a transição “de PAGO para CANCELADO dispara estorno” num sistema que usa o antipadrão de status exige reconstruir manualmente o estado interno do objeto antes de invocar o método sob teste, ao contrário do que ocorreria com uma implementação via State Pattern.
- □ No padrão State, o método responsável por trocar a referência de estado no Contexto (
setEstado) deveria ser público, para que qualquer código cliente externo pudesse forçar uma transição arbitrária sempre que necessário.
- □ Se
EstadoNovo.enviar() lançasse uma exceção informando que o pedido ainda não foi pago, essa exceção estaria expressando uma regra de negócio genuína do domínio, e não um defeito de implementação do padrão State.
- □ Depois de aplicado o State Pattern, a complexidade ciclomática da classe
Pedido aumenta em relação à versão com if/else, porque agora ela precisa gerenciar referências para múltiplas classes de estado diferentes.
- □ Um
Pedido que armazena EstadoPedido estadoAtual e delega pagar() para estadoAtual.pagar(this) permite que o comportamento de pagar() mude completamente ao longo da vida do objeto sem que o código-fonte do método pagar() no Pedido seja alterado uma única vez.
- □ Um sistema de checkout onde o cliente escolhe explicitamente, no carrinho, qual
EstrategiaFrete usar, e onde o Pedido decide sozinho, como consequência de um pagamento bem-sucedido, transitar de EstadoNovo para EstadoPago, está aplicando Strategy e State no mesmo sistema para dois problemas arquiteturais distintos.
- □ Se um engenheiro implementasse o State de forma que um código cliente externo chamasse
pedido.forcarEstado(new EstadoPago()) livremente a qualquer momento, essa implementação preservaria a intenção arquitetural original do padrão da mesma forma que a implementação vista em aula.
- □ A razão pela qual Strategy e State não são o mesmo padrão sob nomes diferentes está exclusivamente no número de métodos que a interface de cada um declara, e não em quem decide a troca de implementação.
- □ Um
ConcreteState que, ao processar uma operação de negócio, decide autonomamente instanciar e ativar o próximo estado da máquina está exercendo o mesmo papel decisório que, no Strategy, pertence ao código cliente que injeta a estratégia no Contexto.
- □ Centralizar a decisão de qual é o próximo estado dentro de um método do Contexto (Opção B) elimina completamente a necessidade de estruturas condicionais em qualquer parte do sistema.
- □ Fazer com que
EstadoNovo conheça diretamente a classe EstadoPago (Opção A) introduz um acoplamento entre classes de estado que é aceitável quando a sequência de transições já reflete uma regra de negócio estável e conhecida.
- □ Uma técnica em que o método de transição retorna o próximo estado (
this.estado = estado.proximo();) permite ao Contexto atualizar sua referência sem conter uma cadeia de if/else comparando o tipo do estado atual.
- □ Se a ordem de transições de um sistema mudasse com muita frequência por decisão de negócio (por exemplo, um fluxo de aprovação configurável por cliente), a Opção A (transição fixa nos estados) seria estruturalmente mais adequada do que uma tabela de transições configurável mantida fora das classes de estado.
- □ O fato de
EstadoPago.cancelar() chamar realizarEstornoFinanceiro() antes de mudar o estado, enquanto EstadoNovo.cancelar() apenas muda o estado sem estornar nada, é uma expressão direta do polimorfismo substituindo uma cadeia de if (status == ...).
- □ Se
EstadoEnviado.cancelar() lança uma exceção proibindo o cancelamento, isso significa que o padrão State, ao contrário do antipadrão original, não é capaz de expressar a regra de negócio “produtos em trânsito não podem ser cancelados”.
- □ Um desenvolvedor que precisa entender o que acontece quando um pedido pago é cancelado, ao usar o State Pattern, encontra essa resposta isolada dentro da classe
EstadoPago, sem precisar ler os outros estados.
- □ Adicionar um novo estado “EM_ANALISE_DE_FRAUDE” ao sistema baseado em State Pattern exige alterar o código-fonte de
EstadoNovo, EstadoPago e EstadoEnviado da mesma forma que exigiria no antipadrão original baseado em String status.
- □ O padrão Adapter resolve especificamente o problema de uma interface externa incompatível com o contrato que o domínio já usa, sem exigir alteração nem do código do fornecedor externo, nem do código cliente que já confia na interface esperada.
- □ Se a equipe decidisse modificar diretamente as classes de negócio centrais para chamar os métodos nativos do sistema legado de auditoria, essa prática produziria o mesmo nível de isolamento arquitetural que o uso de um Adapter produziria.
- □ No
LogAdapter, o parâmetro de severidade fixo (1) passado para registrarLogNoArquivo é um detalhe de tradução que o Adapter absorve, evitando que ele precise ser conhecido por todas as classes de negócio que disparam eventos de log.
- □ Um Adapter, por definição do padrão, deve alterar o comportamento interno de pelo menos uma das duas interfaces que ele está harmonizando para que a tradução funcione corretamente.
- □ Um
LogAdapter implementado como Object Adapter continua funcionando sem nenhuma alteração de código se o objeto LogServicoLegado injetado no construtor for substituído por uma subclasse dele lançada pelo fornecedor após o Adapter já estar em produção.
- □ Um
LogAdapter implementado como Class Adapter (via extends LogServicoLegado) consegue adaptar automaticamente qualquer subclasse futura do legado, porque a herança propaga essa capacidade para toda a árvore de classes.
- □ A principal razão pela qual a indústria prefere o Object Adapter é estética — o código fica mais curto — e não uma diferença estrutural de acoplamento entre as duas abordagens.
- □ Substituir a composição (
private LogServicoLegado legado) por herança (extends LogServicoLegado) no LogAdapter tornaria mais fácil, e não mais difícil, substituir o legado real por um objeto simulado (mock) em um teste automatizado.
- □ Se
EstadoPago chamar pedido.getLogger().info(...) para registrar uma transição, essa classe de estado permanece livre de qualquer conhecimento sobre a existência de LogServicoLegado.
- □ Se o fornecedor do sistema de auditoria legado mudar a assinatura de
registrarLogNoArquivo de (String, int) para (String, String), um sistema que usa LogAdapter corretamente precisaria alterar apenas a classe LogAdapter, sem tocar em nenhuma classe de estado do Pedido.
- □ A combinação de State (para o fluxo interno) e Adapter (para a fronteira externa) na mesma classe
EstadoPago representa uma violação do princípio de que cada classe deve aplicar exatamente um único padrão de projeto.
- □ Um sistema onde
Pedido recebe um LoggerModerno por injeção de dependência, em vez de instanciar diretamente new LogServicoLegado() dentro de suas classes de estado, permite substituir o logger por um mock em testes automatizados sem alterar as classes de estado.