Soluções — O Padrão State e o Padrão Adapter: o Fluxo Seguro do Objeto e a Fronteira Externa
Aula 12 — Programação Orientada a Objetos
Dica(Resposta) Teste 1 — O Antipadrão do Status como Primitivo
- ✔ Verdadeiro — O acoplamento temporal (a obrigação de reconstruir corretamente o estado interno antes de invocar um método) nasce de o método verificar sua própria pré-condição via um campo de estado — isso é independente do tipo desse campo. Trocar
Stringporenumsó elimina valores inválidos, não a estrutura do problema. - ✔ Verdadeiro — É o mesmo antipadrão transferido para outro domínio: a lógica de transição de estado espalhada por vários métodos da mesma classe, em vez de concentrada num único lugar, é o que causa a fragilidade — não importa se o campo se chama
statusoucodigoSituacao, nem se éStringouint. - ✗ Falso — Um campo
String/intverificado por cadeias deif/elsenão oferece nenhuma garantia de exaustividade em tempo de compilação — um método que simplesmente não considera o novo estado compila normalmente e falha silenciosamente em tempo de execução. É exatamente essa ausência de rede de segurança do compilador que agrava a fragilidade do antipadrão. - ✔ Verdadeiro — No antipadrão, só existe uma classe
Pedido; testar o comportamento de “pago” exige montar umPedidointeiro comstatus="PAGO"antes de chamarcancelar(). Com State,EstadoPago.cancelar(pedido)pode ser exercitado isolando a classe de estado diretamente, sem reconstruir o ciclo de vida completo do objeto.
Dica(Resposta) Teste 2 — Estrutura do Padrão State (Context/State/ConcreteState)
- ✗ Falso — Deixar
setEstadolivremente público para qualquer cliente externo reintroduz exatamente o mecanismo de injeção externa que caracteriza o Strategy, esvaziando a intenção do State — que é o próprio fluxo de negócio, através dosConcreteState, decidir e executar suas próprias transições. - ✔ Verdadeiro — É a mesma lógica de Fail-Fast/Design by Contract já estudada: uma pré-condição de negócio (“não se envia o que não foi pago”) sendo enforced no ponto exato onde é violada. A exceção é o padrão funcionando como esperado, não um sintoma de mau design.
- ✗ Falso — O efeito é o oposto: a complexidade ciclomática de
Pedidocai, porque as cadeias condicionais somem dos seus métodos —Pedidopassa a apenas delegar (estadoAtual.pagar(this)). A complexidade não desaparece do sistema, mas se distribui entre classes pequenas e coesas, cada uma com um único caminho de execução. - ✔ Verdadeiro — É o Open/Closed Principle em ação via polimorfismo: o comportamento efetivo de
pagar()muda conformeestadoAtualmuda de referência, mas o código-fonte do métodopagar()doPedido— a linhaestadoAtual.pagar(this)— nunca precisa ser reaberto.
Dica(Resposta) Teste 3 — Strategy vs. State: Mesma Topologia, Intenções Diferentes
- ✔ Verdadeiro — O critério que distingue os dois padrões — quem decide a troca de implementação, e quando — está presente de forma limpa nesse cenário: escolha externa e explícita (Strategy) para o frete, auto-transição interna como efeito colateral de negócio (State) para o ciclo de vida do pedido. Nada impede os dois coexistirem no mesmo sistema, para problemas diferentes.
- ✗ Falso — Essa implementação reintroduz o mecanismo de injeção externa característico do Strategy, esvaziando o ponto central do State: o objeto decidir e mudar sua própria “personalidade” como efeito colateral do seu próprio fluxo de negócio, não por ordem externa arbitrária.
- ✗ Falso — É exatamente o oposto: a estrutura de classes (interface + implementações concretas) é necessária, mas não suficiente, para definir o padrão. O que realmente diferencia Strategy de State é quem decide qual implementação está ativa e quando — não a forma sintática da interface.
- ✔ Verdadeiro — É a inversão exata do lugar da decisão entre os dois padrões: no Strategy, o cliente externo decide e injeta; no State, o próprio
ConcreteStateassume esse papel decisório internamente, como consequência do processamento de negócio.
Dica(Resposta) Teste 4 — Onde Colocar a Lógica de Transição
- ✗ Falso — Centralizar a decisão no Contexto não elimina condicionais — apenas as move para um único lugar (o método do Contexto que decide o próximo estado), em vez de removê-las do sistema por completo.
- ✔ Verdadeiro — Todo design é uma troca: acoplar
EstadoNovoaEstadoPagodiretamente custa flexibilidade, mas ganha simplicidade — uma troca aceitável quando a sequência de transições é uma regra de negócio fixa e já conhecida, não algo que mude com frequência. - ✔ Verdadeiro — Delegar ao próprio estado a responsabilidade de determinar (e retornar) o próximo estado é o que permite ao Contexto ficar com uma única linha de atribuição polimórfica, sem precisar perguntar “que tipo de estado é esse?” através de uma cadeia condicional.
- ✗ Falso — É o oposto: quando a sequência de transições muda com frequência por decisão de negócio, fixá-la no código-fonte das classes de estado (Opção A) exige reabrir e recompilar classes a cada mudança — uma tabela de transições configurável, mantida fora do código, se adapta sem exigir nova compilação.
Dica(Resposta) Teste 5 — O Ciclo de Vida Robusto e o Polimorfismo em Ação
- ✔ Verdadeiro — Cada
ConcreteStateimplementacancelar()com o comportamento específico que faz sentido para aquele estado — sem que nenhum código precise perguntar “qual é o status atual?” antes de decidir o que fazer. Isso é exatamente o papel do polimorfismo substituindo condicionais. - ✗ Falso — É exatamente o oposto: lançar a exceção é a forma como o State expressa essa regra de negócio, de forma isolada e explícita dentro de
EstadoEnviado, sem misturar essa lógica com o comportamento de nenhum outro estado. - ✔ Verdadeiro — Essa localidade de leitura é uma das vantagens centrais do State sobre o antipadrão: o comportamento de “cancelar quando pago” mora inteiramente em
EstadoPago.cancelar(), sem exigir que o leitor percorra um método monolítico com ramos para todos os estados possíveis. - ✗ Falso — No antipadrão, os três métodos do
Pedidomonolítico precisam ser reabertos igualmente. Com State, só as classes diretamente envolvidas na transição para/a partir do novo estado precisam mudar (tipicamente sóEstadoNovo, que passaria a poder transitar para o novo estado) — estados sem relação direta, comoEstadoEnviado, podem permanecer intocados.
Dica(Resposta) Teste 6 — O Dilema da Incompatibilidade e o Papel do Adapter
- ✔ Verdadeiro — É a definição central do padrão: o Adapter absorve a tradução numa terceira classe nova, preservando intacto tanto o código do fornecedor (muitas vezes nem sequer disponível para alteração) quanto o código cliente já escrito contra a interface esperada.
- ✗ Falso — Modificar as classes de negócio para chamar diretamente a API do legado contamina o modelo de domínio com detalhes de infraestrutura voláteis — exatamente o oposto do isolamento que o Adapter produz ao concentrar essa dependência numa única classe de fronteira.
- ✔ Verdadeiro — Detalhes de tradução como códigos numéricos fixos exigidos pela API legada são exatamente o tipo de “número mágico” que o Adapter encapsula, evitando que se espalhe por todas as classes de negócio que precisam registrar eventos.
- ✗ Falso — O Adapter nunca altera o comportamento interno de nenhum dos dois lados — ele só traduz chamadas na fronteira entre eles, delegando para a implementação real depois de adaptar a assinatura/formato da chamada.
Dica(Resposta) Teste 7 — Object Adapter vs. Class Adapter
- ✔ Verdadeiro — Por composição, o Adapter só depende da API pública declarada pelo tipo
LogServicoLegado— qualquer subclasse válida (presente ou futura) satisfaz esse contrato via polimorfismo, sem exigir nenhuma alteração noLogAdapter. - ✗ Falso — É o oposto:
extendsfixa o Adapter, em tempo de compilação, a uma única classe-mãe específica. Uma nova subclasse lançada pelo fornecedor exigiria escrever um Class Adapter dedicado a ela — a herança não propaga essa capacidade automaticamente. - ✗ Falso — A preferência é estrutural, não estética: o Object Adapter (composição) desacopla o Adapter da implementação concreta do legado, permite reaproveitar subclasses futuras e facilita testes com mocks — vantagens de acoplamento, não de brevidade de código.
- ✗ Falso — É o oposto: composição via injeção de construtor é o que permite passar um mock no lugar do objeto real em teste; substituir por herança elimina esse ponto de injeção, tornando a substituição por mock mais difícil, não mais fácil.
Dica(Resposta) Teste 8 — Estudo de Caso: Unificação de Log e a Fronteira com o State
- ✔ Verdadeiro —
EstadoPagoconhece só a abstração de logging exposta peloPedido— oLogAdapter(e oLogServicoLegadoque ele encapsula) fica inteiramente escondido atrás dessa fronteira, exatamente o isolamento que o Adapter existe para produzir. - ✔ Verdadeiro — Toda a dependência do formato exato da API legada está concentrada dentro do
LogAdapter— uma mudança de assinatura do lado do fornecedor se resolve editando só essa classe de fronteira, sem se propagar para as classes de estado que só conhecem a interface abstrata de logging. - ✗ Falso — Não existe tal princípio: padrões de projeto resolvem problemas arquiteturais ortogonais entre si, e nada impede uma mesma classe de participar de mais de um padrão simultaneamente — aqui,
EstadoPagoparticipa do State (decidindo sua própria transição) e, ao delegar uma chamada de log, se beneficia do Adapter que outra classe implementa. - ✔ Verdadeiro — É a mesma vantagem de testabilidade já vista com Strategy, Factory Method e o próprio Adapter: injetar a dependência via construtor, em vez de instanciá-la diretamente, é o que abre espaço para trocar a implementação real por um mock em teste, sem tocar no código das classes de estado.