Índice do grimório

Padrão 08 · Estruturais

Decorator

compor efeitos de ataque e observar como a ordem das camadas muda o resultado.

Começar pelo código
A tecelã de geada estende seus oito apêndices sobre Dara e Nessa enquanto elas abrem uma faixa estreita no gelo do túnel.
Cena do capítulo 08 · Decorator

01 · Entenda a escolha

Os fundamentos do padrão.

README.md

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: Component Ataque e seu resultado.
  • src/espada.ts: Concrete Component Espada, 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/ e solution/: 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 = 26 e registra espada → calor +3 → amplificação x2.
  • new MantoDeCalor(new MantoAmplificador(espada, 2), 3) calcula 10 * 2 + 3 = 23 e registra espada → 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.

src/main.tsTypeScript
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)}`);
A execução do programa é feita localmente.

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.

Espada realiza Ataque.
08 · a componente

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.

MantoDeCalor realiza Ataque.
08 · b calor

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

MantoAmplificador realiza Ataque.
08 · c amplificador

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.

exercise/README.md

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 solution
Baixar este projeto completo

Diagrama ampliado