Padrão 14 · Comportamentais
State
variar ações e transições conforme o estado atual de um guerreiro.
Começar pelo código
01 · Entenda a escolha
Os fundamentos do padrão.
14 — State: O Livro das Formas Mutáveis
Projeto independente em TypeScript. Guerreiro conserva sua identidade e delega atacar() e descansar(condicao) ao objeto Estado atual. Cada estado devolve uma Transicao; o contexto aplica a decisão e registra dados consultáveis, inclusive recusas. A classe e o protótipo do guerreiro não são trocados.
O nome usado neste projeto é Dário, conforme ../../docs/adendo-nomes-personagens-01.md. O planejamento congelado permanece como registro histórico; este exemplo aplica a decisão nominal do adendo.
Executar
Ambiente declarado e utilizado: Node 26.10.0, npm 11.19.1, TypeScript 5.9.3 e @types/node 26.0.0. Use a versão em .nvmrc; o lock pertence somente a este projeto.
Na pasta book-23/code/14-state:
npm ci --ignore-scripts --no-audit --no-fund
npm test
npm start
npm run solution
Na raiz do repositório:
node book-23/scripts/projetos.mjs 14 capture
node book-23/scripts/projetos.mjs 14 verify
O runner compila e testa somente este projeto. evidence/stdout.txt e evidence/solution-stdout.txt preservam saídas reais, sem os banners do npm. evidence/manifest.json relaciona hashes dos fontes e saídas; evidence/isolation.json registra uma cópia temporária executada sem acesso aos outros projetos do livro. Capturar novamente produz novas evidências; não substitui uma revisão independente.
Política do ciclo
new Guerreiro("Dário")começa normal, com zero intervalos de fúria.- O primeiro
atacar()executa um golpe normal e entra em fúria com dois intervalos intactos. - O ataque seguinte executa um golpe furioso e deixa um intervalo.
- Outro ataque executa o segundo golpe furioso e entra em exaustão, com zero intervalos.
- Em exaustão, todo ataque é recusado: a tentativa aparece no histórico com
executada: false, sem efeito de golpe nem consumo adicional. - Somente ataques furiosos consomem os intervalos. Tentar descansar em fúria é recusado e preserva o contador, com qualquer condição de proteção.
- Em exaustão,
descansar({ protegido: true })representa um período completo de repouso protegido já cumprido no domínio externo e retorna ao normal. Sem proteção informada, recusa. No normal, o mesmo repouso protegido é aceito e conserva o estado; sem proteção, recusa.
O booleano é uma informação recebida pelo modelo. O programa não mede duração, autentica proteção, simula ameaça, cura o corpo ou comprova repouso real. Não há temporizador nem reserva física ou mágica sendo subtraída. efeito descreve a ação modelada, sem operar objetos físicos.
RegistroAcao contém ação, aceitação, estado anterior, estado posterior, intervalos restantes e efeito. situacao() devolve um snapshot congelado. O histórico devolve uma nova lista de registros congelados; alterações na lista devolvida não alteram a lista interna. O programa funcional mostra três golpes e uma tentativa recusada, seguida de dois pedidos de descanso, um recusado e outro aceito.
Estrutura e limites da extensão
src/contratos.ts:Estado,Transicao, condição de descanso e registro.src/estados.ts:EstadoNormal,EstadoFuriosoeEstadoExausto.src/guerreiro.ts: contexto que delega, aplica a transição e guarda histórico.src/tabela.ts: alternativa menor com tabela/switch, exercitada contra todas as combinações de estados e ações da base.solution/defensivo.ts: extensão separada do exercício.diagram.mmd: relações editáveis da implementação base.
Os estados fornecidos são objetos congelados com campos escalares. Consumir fúria produz outro objeto de estado; compartilhar o estado inicial entre dois guerreiros não compartilha um contador mutável. O contexto não expõe setEstado nem um método público de aplicação: #estado e #aplicar são privados em execução JavaScript. A injeção de um estado inicial é uma escolha de composição feita na construção, sem troca pública durante o ciclo.
As regras do ciclo valem para as implementações fornecidas sem adulteração externa. O contrato permite outras implementações de Estado: o TypeScript não prova que elas sejam imutáveis, honestas ou livres de efeitos. Uma extensão pode definir outras regras. Exceções lançadas por um estado são propagadas sem acrescentar registro ou aplicar uma transição; isso não desfaz efeitos que a própria extensão tenha realizado fora do contexto. Não há promessa de fronteira de segurança contra código arbitrário que modifique protótipos ou dependências.
Cada ação da base consulta um único estado e acrescenta um registro. O histórico cresce com todas as tentativas; listarHistorico() copia essa lista e custa proporcionalmente ao seu tamanho. A criação de estados imutáveis tem custo de alocação. Não há medição de desempenho nem afirmação de ganho.
Quando usar outra solução
src/tabela.ts é uma alternativa legítima para estas poucas regras: mantém a transição visível sem distribuir comportamento em classes. Uma máquina de estados finita pode ser implementada assim, sem usar State GoF. State passa a ajudar quando os comportamentos e suas transições se organizam melhor em objetos coesos; não exige uma classe para cada valor de uma variável.
Strategy encapsula políticas ou algoritmos intercambiáveis para um objetivo. Aqui as mesmas ações passam a ter comportamentos próprios de uma etapa do ciclo de vida, e as decisões produzem transições. A intenção e as regras diferenciam os padrões; quem inicia uma troca não constitui uma regra absoluta de distinção. Ambos podem usar composição e delegação.
Exercício
Leia exercise/README.md. A solução implementa um estado defensivo selecionado na construção, com contra-ataque e saída por repouso protegido. Nenhum comando para entrar em defesa durante um ciclo em andamento é implementado. O custo e esse limite estão explicitados no exercício, sem abrir um atalho para apagar a exaustão.
02 · Leia o mecanismo
O feitiço, linha por linha.
Comece por src/main.ts para ver o programa em uso. Explore os contratos, a implementação e os testes no seletor de arquivos.
import type { RegistroAcao } from "./contratos.js";
import { Guerreiro } from "./guerreiro.js";
const dario = new Guerreiro("Dário");
const imprimir = (registro: RegistroAcao): void => {
console.log(`${registro.acao}: ${registro.executada ? "executada" : "recusada"} | ${registro.antes} -> ${registro.depois} | fúria restante ${registro.intervalosFuriaRestantes}`);
};
console.log(`Vigia: ${dario.nome}`);
imprimir(dario.atacar());
imprimir(dario.atacar());
imprimir(dario.atacar());
imprimir(dario.atacar());
imprimir(dario.descansar({ protegido: false }));
imprimir(dario.descansar({ protegido: true }));
console.log(`Ataques executados no histórico: ${dario.listarHistorico().filter((registro) => registro.acao === "atacar" && registro.executada).length}`);
03 · Revele as conexões
A estrutura por trás da magia.
Veja os papéis e suas relações. Amplie cada vista para ler atributos, métodos e conexões com calma.
Esta vista reúne Guerreiro, Estado. Guerreiro mantém referência a Estado (delega ações). Guerreiro: Estado inicial injetável somente na construção; aplicação da transição privada em execução. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e1 · delega ações
Esta vista reúne Estado, Transicao. Estado depende de Transicao (devolve decisão). Transicao mantém referência a Estado (próximo). Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e5 · devolve decisão e6 · próximo
Esta vista reúne Estado, EstadoNormal, EstadoFurioso, EstadoExausto. EstadoNormal realiza Estado. EstadoFurioso realiza Estado. EstadoExausto realiza Estado. EstadoExausto depende de EstadoNormal (repouso protegido completo). Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e10 · repouso protegido completo
Esta vista reúne EstadoNormal, EstadoFurioso, EstadoExausto. EstadoNormal depende de EstadoFurioso (golpe normal inicia 2). EstadoFurioso depende de EstadoFurioso (golpe furioso 2 para 1). EstadoFurioso depende de EstadoExausto (golpe furioso 1 para 0). EstadoFurioso: Imutável. Descanso recusado preserva intervalos. EstadoExausto: Ataque recusado. Sem proteção informada, descanso recusado. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e7 · golpe normal inicia 2 e8 · golpe furioso 2 para 1 e9 · golpe furioso 1 para 0
04 · Faça sua escolha
Sua vez de conjurar.
Exercício — Estado defensivo
Acrescente EstadoDefensivo ao contrato Estado, em arquivo separado. Componha um novo guerreiro inicialmente nesse estado. Não crie um setEstado público para contornar o ciclo.
Critérios verificáveis:
- O estado defensivo começa com zero intervalos de fúria.
atacar()executa um contra-ataque controlado e retorna ao normal, sem iniciar fúria nesse mesmo ato.- Descansar sem proteção é recusado e mantém a defesa. Um período completo de descanso protegido, informado conforme o contrato da base, retorna ao normal.
- Depois do contra-ataque, o próximo ataque normal inicia dois intervalos. Os dois ataques furiosos seguintes ainda terminam em exaustão; a extensão não modifica essa regra.
- Registre quais arquivos, interfaces e transições existentes precisaram mudar. Diferencie a seleção inicial de uma entrada em defesa durante uma execução já iniciada.
Resposta comentada e executável
../solution/defensivo.ts acrescenta uma classe que implementa a interface existente. ../solution/main.ts seleciona esse estado pelo parâmetro de construção já disponível em Guerreiro; ../solution/defensivo.test.ts verifica contra-ataque, repouso e retomada integral do ciclo.
Para esta variante inicial, nenhuma interface, implementação do contexto ou decisão dos três estados da base foi alterada. Foram acrescentados a classe, o código de composição e seus testes. A extensão usa a transição já definida para voltar ao normal. Isso não prova que qualquer nova transição possa ser incluída sem mudanças: para permitir uma ação pública defender() durante o ciclo, seria necessário definir o novo comando no contrato e no contexto, decidir em quais estados ele é permitido e implementar/testar essas decisões nos estados envolvidos. A solução não implementa essa entrada dinâmica e não promete permitir defesa quando exausto.
Execute npm run solution e npm test na pasta deste projeto. O diagrama principal representa a base; esta classe adicional implementa Estado e pode transitar para EstadoNormal.
Compare depois da sua tentativa.
A resposta e seus testes estão na pasta solution/. O sucesso dos testes fornecidos confirma essa resposta; sua tentativa precisa estar incluída na compilação para ser avaliada.
Do estudo à prática
Experimente localmente.
Node 26.10.0 · npm 11.19.1 · TypeScript 5.9.3
Execute os comandos dentro da pasta deste projeto. O lock instala o compilador local. Os programas não são executados no navegador.
cd book-23/code/14-state npm ci --ignore-scripts --no-audit --no-fund npm test npm start # Depois da sua tentativa npm run solutionBaixar este projeto completo