Padrão 08 · Estruturais
Decorator
compor efeitos de ataque e observar como a ordem das camadas muda o resultado.
Começar pelo código
01 · Entenda a escolha
Os fundamentos do padrão.
08 — Decorator: Combinação de efeitos de combate
Ataque.executar() devolve uma potência e uma sequência de efeitos. Espada funciona como núcleo; MantoDeCalor e MantoAmplificador envolvem outro Ataque, mantêm a mesma interface e modificam seu resultado sem alterar o objeto interno.
Executar
Node 26.10.0, npm 11.19.1, TypeScript 5.9.3, @types/node 26.0.0, lock próprio. Dentro deste diretório:
npm ci --ignore-scripts
npm test
npm start
npm run solution
Da raiz Books:
node book-23/scripts/projetos.mjs 08 install
node book-23/scripts/projetos.mjs 08 check
node book-23/scripts/projetos.mjs 08 capture
node book-23/scripts/projetos.mjs 08 verify
O build limpa dist, compila ES2022/ESM com TypeScript estrito e executa os testes Node sobre JavaScript emitido. Nenhum projeto vizinho é importado. exercise/ fica separado da solução e fora do build oficial.
Papéis e fontes
src/contratos.ts: ComponentAtaquee seu resultado.src/espada.ts: Concrete ComponentEspada, utilizável sozinho.src/mantos.ts: Concrete Decorators por composição; não há subclasses para cada combinação.src/resultado.ts: validação numérica e cópia/congelamento dos valores de saída.src/direto.ts: função executável para a alternativa de fórmula fixa, sem wrappers.src/main.ts: duas ordens, núcleo, composição reconstruída e alternativa direta.test/composicao.test.ts: aritmética, ordem, reuso, duplicação de camadas, delegação e limites.exercise/esolution/: efeito limitador não comutativo, demonstração e testes.diagram.mmd: fonte lógica editável do código-base; renderização e QA visual pertencem à etapa de arte.evidence/: stdout real, testes, manifesto e execução em cópia isolada.
Ordem e resultados observáveis
O núcleo tem potência 10, o calor soma 3 e a amplificação multiplica por 2. A operação delega primeiro ao componente interno e aplica a camada externa depois:
new MantoAmplificador(new MantoDeCalor(espada, 3), 2)calcula(10 + 3) * 2 = 26e registraespada → calor +3 → amplificação x2.new MantoDeCalor(new MantoAmplificador(espada, 2), 3)calcula10 * 2 + 3 = 23e registraespada → amplificação x2 → calor +3.
Cada wrapper chama o interno uma vez e acrescenta sua responsabilidade. Resultados são valores retornados e testados, não apenas mensagens de console. Os mantos não deduplicam camadas: dois calores +3 produzem 16; dois amplificadores ×2 produzem 40 sobre o núcleo 10.
As implementações fornecidas não acumulam estado entre execuções. Cada chamada cria outro resultado e outra lista de efeitos, congelados com Object.freeze. Strings na lista não exigem outra camada de cópia. Mesmo quando um colaborador externo devolve uma lista mutável, o decorator copia a lista e não edita o resultado recebido. Isso não promete tornar determinístico ou imutável qualquer Ataque externo com estado próprio.
Retirada e fronteiras numéricas
Retirar um manto é construir outra composição a partir das referências que o cliente preservou. new MantoDeCalor(espada, 3) devolve 13 sem editar a composição anterior de 26. A espada continua devolvendo 10. Não há desempilhar() que descubra e modifique silenciosamente uma cadeia existente, nem restauração de objetos destruídos na narrativa.
Potência e bônus devem ser inteiros seguros não negativos; fator deve ser inteiro seguro pelo menos 1. Potência zero, bônus zero e fator 1 são válidos. Frações, negativos, NaN, infinitos e valores fora da faixa de inteiros seguros são rejeitados. O resultado interno é validado antes da operação; cada resultado aritmético é validado antes de ser devolvido. Se a adição ou multiplicação sair da faixa permitida, a chamada lança erro em vez de publicar um valor impreciso como válido. O código usa number, não BigInt nem aproximação de quantidades fracionárias.
Esses valores são índices abstratos de um modelo fictício, sem unidade de energia, força ou temperatura. O cálculo não garante resistência de um túnel, segurança física, sucesso de resgate ou dano real. Os campos internos usam # em execução; entradas e colaboradores obedecem ao contrato tipado, não são um parser de JSON arbitrário.
Custos e alternativas
Wrappers acrescentam objetos e uma ordem de execução que pode ser difícil de rastrear em cadeias longas. O cliente precisa conservar referências e saber qual composição quer reconstruir. Se a fórmula sempre for soma e depois multiplicação, calcularPotenciaDireta(10, 3, 2) resolve com menos estrutura e é testada produzindo 26. Essa função não expõe combinações dinâmicas ou histórico de efeitos; não é anunciada como equivalente para todos os usos dos decorators.
Proxy pode oferecer o mesmo contrato para controlar acesso, carregamento ou encaminhamento ao serviço. Aqui a intenção é acrescentar responsabilidades de cálculo, não decidir quem pode atacar. Chain of Responsibility normalmente encaminha ou decide o tratamento de uma solicitação; esta composição acumula responsabilidades e devolve o resultado do encadeamento interno. A ordem de métodos ou o nome middleware não basta para classificar uma API: não se afirma que todo middleware seja Decorator.
Captura e hashes ligam saídas às fontes executadas. Não substituem auditoria técnica independente nem aprovação autoral; não há benchmark ou promessa de desempenho.
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 { Ataque } from "./contratos.js";
import { calcularPotenciaDireta } from "./direto.js";
import { Espada } from "./espada.js";
import { MantoAmplificador, MantoDeCalor } from "./mantos.js";
const espada = new Espada(10);
const somaAntesDeMultiplicar = new MantoAmplificador(new MantoDeCalor(espada, 3), 2);
const multiplicaAntesDeSomar = new MantoDeCalor(new MantoAmplificador(espada, 2), 3);
// Retirar é reconstruir outra composição, sem editar a já existente.
const somenteCalor = new MantoDeCalor(espada, 3);
for (const [nome, ataque] of [
["Núcleo", espada],
["Calor -> amplificação", somaAntesDeMultiplicar],
["Amplificação -> calor", multiplicaAntesDeSomar],
["Sem amplificador", somenteCalor],
] satisfies [string, Ataque][]) {
const resultado = ataque.executar();
console.log(`${nome}: ${resultado.potencia} | ${resultado.efeitos.join(" -> ")}`);
}
console.log(`Composição anterior preservada: ${somaAntesDeMultiplicar.executar().potencia}`);
console.log(`Fórmula direta (10 + 3) * 2: ${calcularPotenciaDireta(10, 3, 2)}`);
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 Ataque, Espada, ResultadoAtaque. Espada realiza Ataque. Ataque depende de ResultadoAtaque. ResultadoAtaque: Valor e lista congelados. Inteiros seguros. Ordem registrada nos efeitos. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos.
Esta vista reúne Ataque, MantoDeCalor. MantoDeCalor realiza Ataque. MantoDeCalor mantém referência a Ataque (envolve e delega). MantoDeCalor: Delega uma vez; soma bônus; acrescenta efeito sem mudar resultado interno. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e4 · envolve e delega
Esta vista reúne Ataque, MantoAmplificador. MantoAmplificador realiza Ataque. MantoAmplificador mantém referência a Ataque (envolve e delega). MantoAmplificador: Delega uma vez; multiplica; ordem interna precede camada externa. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e5 · envolve e delega
04 · Faça sua escolha
Sua vez de conjurar.
Exercício — O limite também tem posição
Crie MantoLimitador, um wrapper que implementa Ataque, recebe um ataque interno e um teto inteiro seguro não negativo. Sua potência é Math.min(potenciaInterna, teto) e o resultado acrescenta o efeito limite N depois dos efeitos internos.
Critérios verificáveis:
- Com núcleo 10, calor +3 e teto 11, calor → limite produz 11; limite → calor produz 13.
- Cada ordem preserva o histórico correto dos efeitos e não modifica a espada.
- O teto precisa obedecer às mesmas fronteiras de representação numérica do projeto.
- Teto zero é válido; o resultado é congelado pelo mesmo helper.
- Uma camada externa de limite não pode ocultar um overflow já recusado pelo componente interno.
- Explique por que limitar antes de somar não garante teto para o resultado final.
Compare com solution/limitador.ts e solution/limitador.test.ts. Execute npm run solution. Tentativas em exercise/ não são compiladas com a resposta oficial.
Resposta comentada
O novo decorator conserva o contrato Ataque e usa composição. Ele limita somente o resultado que recebe naquele ponto. Por isso min(10 + 3, 11) = 11, mas min(10, 11) + 3 = 13. A lista de efeitos revela a ordem, em vez de registrar apenas o número final.
Uma composição que já estourou a faixa permitida lança erro antes de entregar um resultado ao limitador. O wrapper não recupera essa execução nem desfaz efeitos de um componente externo arbitrário. Nas implementações fornecidas, executar só calcula valores e não muda o núcleo ou qualquer camada.
Para uma fórmula fixa e conhecida, uma função é uma alternativa menor. O teste de calcularPotenciaDireta() no projeto-base demonstra isso. Os wrappers acrescentam valor quando a combinação de responsabilidades precisa ser montada e reutilizada por meio do contrato comum.
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/08-decorator npm ci --ignore-scripts --no-audit --no-fund npm test npm start # Depois da sua tentativa npm run solutionBaixar este projeto completo