Índice do grimório

Padrão 05 · Criacionais

Singleton

escolher o ciclo de vida de um registro, com acesso compartilhado por avaliação do módulo e dependências injetadas.

Começar pelo código
Larvas de casca granítica prendem-se à engrenagem molhada e à chave de Oren enquanto ele mantém a trava e um trabalhador as retira.
Cena do capítulo 05 · Singleton

01 · Entenda a escolha

Os fundamentos do padrão.

README.md

05 — Singleton: Registro com ciclo de vida controlado

Uma casa de controle usa um registro de ordens. A composição escolhe se cria uma instância explícita ou obtém uma instância compartilhada da API do módulo. Os serviços recebem Registro por injeção; suas preferências de turno pertencem a cada equipe.

Ambiente e execução

Node 26.10.0, npm 11.19.1, TypeScript 5.9.3, @types/node 26.0.0, dependências fixadas por lock. Dentro deste projeto:

npm ci --ignore-scripts
npm test
npm start
npm run solution

Da raiz Books:

node book-23/scripts/projetos.mjs 05 install
node book-23/scripts/projetos.mjs 05 check
node book-23/scripts/projetos.mjs 05 capture
node book-23/scripts/projetos.mjs 05 verify

O build limpa dist e compila módulos ES2022/ESM com TypeScript estrito. Testes usam node:test sobre JavaScript compilado. Os Workers executam arquivos emitidos locais, não TypeScript cru. Não há importações de outros capítulos. exercise/ fica fora do build oficial.

Papéis e arquivos

  • src/contratos.ts: contrato Registro e dados de ordens.
  • src/registro.ts: RegistroEmMemoria, sem restrição de construtor; aceita new para ciclos locais e testes.
  • src/equipe.ts: serviço com dependência explícita e preferência de turno por instância.
  • src/modulo-compartilhado.ts: armazenamento privado ao módulo e acesso síncrono à referência compartilhada.
  • src/composicao.ts: duas equipes recebem o registro escolhido pelo chamador.
  • src/preferencia-compartilhada.ts: contraprova isolada do problema de compartilhar preferências mutáveis.
  • src/sonda-worker.ts e src/contextos.ts: outro contexto executa o mesmo módulo e comunica contagens por mensagem.
  • src/main.ts: alternativa simples primeiro, identidade da API, preferências e fronteira entre contextos.
  • test/: identidade, substituição, isolamento, histórico, falhas e dois Workers.
  • solution/: raiz que preserva o ciclo compartilhado padrão e aceita substituição explícita.
  • diagram.mmd: relações editáveis; caixas com estereótipo module representam arquivos de módulo, não classes inexistentes.
  • evidence/: stdout real, testes, manifesto e execução numa cópia isolada.

O que é único aqui

obterRegistroCompartilhado() mantém uma instância de RegistroEmMemoria na variável do módulo. Chamadas que usam a mesma avaliação desse módulo no mesmo contexto devolvem a mesma referência. É a implementação por módulo de um ciclo singleton e de seu ponto de acesso. A classe RegistroEmMemoria continua construtível; não se afirma que só um objeto dessa classe possa existir.

O getter é síncrono e não suspende entre consultar, criar e devolver a instância. Não há corrida assíncrona reproduzida, mutex ou inicialização de rede neste projeto. Uma futura inicialização com await precisaria de uma política própria para chamadas simultâneas, falhas e tentativas; não é uma feature escondida desta implementação.

A unicidade da API não abrange todo o processo de implantação, máquinas, processos ou todos os contextos JavaScript. O teste executa dois Workers reais no Node 26.10.0: cada Worker observa registro inicialmente vazio, acrescenta uma ordem e obtém a mesma referência em suas próprias chamadas. O histórico do pai não muda. A demonstração principal preserva suas duas ordens enquanto outro Worker registra apenas uma em seu contexto. Nenhuma referência de objeto é comparada entre heaps; comparamos identidade dentro de cada contexto e resultados enviados por mensagem.

Não há teste de processos separados, outros realms ou cópias de módulos por URLs distintas; não prometemos coordenação entre eles. A política deve ser reconciliada com o carregador e a implantação efetivos. O projeto não impõe unicidade distribuída.

Estado de negócio e dependência

O registro de ordens compartilhado não contém o turno preferido de todas as equipes. EquipeDeComportas.definirTurno() afeta só aquela instância; ordens anteriores são valores congelados e preservam o turno de emissão. A contraprova usa deliberadamente duas referências ao mesmo objeto de preferências e mostra a contaminação. Esse cenário é criado localmente a cada chamada e não altera a instância padrão; ilustra uma falha de compartilhamento que pode ocorrer tanto com Singleton quanto com injeção mal composta.

O serviço não importa o provedor do módulo. A raiz decide o ciclo de vida e pode continuar injetando um singleton. Injeção torna a dependência explícita e substituível no serviço; não obriga a criar uma nova instância a cada uso e não muda sozinha a escolha de ciclo de vida. new RegistroEmMemoria() na raiz é suficiente quando não é necessária uma API global de acesso.

Os testes de comportamento criam registros novos ou um substituto local por caso. Os únicos testes que usam a API compartilhada verificam identidade e ausência de alterações vindas dos Workers; não dependem de esvaziar esse registro. Não existe reset() global para mascarar ordem de execução.

Contrato e limites

emitirOrdem() valida o nome da comporta, registra um valor com equipe/ação/turno e só então retorna. Se o registro falha, o erro é propagado. RegistroEmMemoria normaliza nomes, copia entradas, congela os valores e devolve uma lista defensiva. As preferências e o estado do registro usam campos # em execução; a variável de instância compartilhada pertence ao escopo léxico do módulo.

Não se usa private TypeScript como barreira de segurança nem se presume que readonly impeça mutação em JavaScript. A API recebe valores tipados, não valida JSON arbitrário, e não é um sistema de autorização. Registrar uma ordem não abre uma comporta física, não confirma execução, não arbitra conflitos, não persiste dados nem impede perda de memória ao encerrar o processo. O exemplo não oferece transações ou sincronização distribuída.

Referências e evidência

Fontes primárias consultadas em 22/10/2026: Node.js — ECMAScript modules, cache próprio do carregador ESM e Node.js — Worker threads. A documentação corrente consultada se identifica como Node 26.12.1; a reprodução está fixada em 26.10.0. As observações de cache/identidade e isolamento deste projeto foram testadas nessa versão fixada, sem assumir equivalência integral entre as versões.

Captura e hashes ligam o stdout ao código executado, sem substituir auditoria técnica independente ou aprovação autoral. Não há benchmark, recurso externo único ou alegação de ganho 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 { montarCasaDeControle } from "./composicao.js";
import { observarOutroContexto } from "./contextos.js";
import { obterRegistroCompartilhado } from "./modulo-compartilhado.js";
import { criarExemploDePreferenciaGlobal } from "./preferencia-compartilhada.js";
import { RegistroEmMemoria } from "./registro.js";

// Alternativa simples: a composição escolhe uma instância sem acesso global.
const casaLocal = montarCasaDeControle(new RegistroEmMemoria());
const outraCasa = montarCasaDeControle(new RegistroEmMemoria());
console.log(`Instâncias explícitas distintas: ${casaLocal.registro !== outraCasa.registro}`);

// A API compartilhada só é chamada na raiz; os serviços recebem Registro.
const registro = obterRegistroCompartilhado();
console.log(`API compartilhada mesma instância: ${registro === obterRegistroCompartilhado()}`);
const casa = montarCasaDeControle(registro);

const armadilha = criarExemploDePreferenciaGlobal();
armadilha.sul.turno = "noite";
console.log(`Preferência compartilhada indevida: Norte herdou ${armadilha.norte.turno}`);

casa.equipeSul.definirTurno("noite");
const sul = casa.equipeSul.emitirOrdem("Leste", "fechar");
const norte = casa.equipeNorte.emitirOrdem("Oeste", "abrir");
console.log(`Preferências locais: Sul ${sul.turno} | Norte ${norte.turno}`);
console.log(`Ordens no registro da casa: ${registro.listarOrdens().length}`);

const sonda = await observarOutroContexto();
console.log(`Outro Worker: antes ${sonda.antes} | depois ${sonda.depois} | mesma API ${sonda.mesmaIdentidade}`);
console.log(`Registro da casa após Worker: ${registro.listarOrdens().length}`);
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.

RegistroEmMemoria realiza Registro.
05 · a registro

Esta vista reúne Registro, RegistroEmMemoria. RegistroEmMemoria realiza Registro. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos.

ModuloCompartilhado mantém referência a RegistroEmMemoria (cria uma vez nesta avaliação).
05 · b módulo

Esta vista reúne Registro, RegistroEmMemoria, ModuloCompartilhado. ModuloCompartilhado mantém referência a RegistroEmMemoria (cria uma vez nesta avaliação). ModuloCompartilhado depende de Registro (devolve mesma referência). ModuloCompartilhado: Node ESM: módulo, não classe. Worker separado avalia seu próprio módulo. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e5 · cria uma vez nesta avaliação e6 · devolve mesma referência

EquipeDeComportas mantém referência a Registro (recebe no construtor).
05 · c composição

Esta vista reúne Registro, EquipeDeComportas, Composicao. EquipeDeComportas mantém referência a Registro (recebe no construtor). Composicao depende de EquipeDeComportas (cria duas equipes). Composicao depende de Registro (recebe ciclo escolhido). Composicao: main escolhe new ou API compartilhada e injeta a dependência. EquipeDeComportas: Preferência de turno pertence à equipe. Ordem registra intenção local. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos. e2 · recebe no construtor e3 · cria duas equipes e4 · recebe ciclo escolhido

04 · Faça sua escolha

Sua vez de conjurar.

exercise/README.md

Exercício — A dependência sai do esconderijo

A base já injeta Registro em EquipeDeComportas e recebe essa dependência em montarCasaDeControle(registro). Acrescente uma função de entrada iniciarCasa() que escolhe o ciclo padrão na raiz e também aceita uma instância explícita para teste ou outra casa de controle. O serviço fornecido não contém acesso global a ser removido.

Critérios verificáveis:

  • Serviços não importam o módulo de acesso compartilhado.
  • Duas composições com o ciclo padrão recebem a mesma referência no mesmo contexto de módulo.
  • Passar dois new RegistroEmMemoria() diferentes mantém históricos independentes.
  • Mudar o turno da equipe Sul não altera a equipe Norte, embora ambas possam compartilhar o registro de ordens.
  • Testes não precisam apagar ou reiniciar estado global.
  • Explique o que continua singleton ao acrescentar a escolha padrão na raiz, mantendo os serviços sem acesso ao módulo compartilhado.

Compare com solution/raiz.ts e solution/raiz.test.ts; execute npm run solution. Tentativas em exercise/ não participam do build oficial.

Resposta comentada

iniciarCasa(registro = obterRegistroCompartilhado()) concentra a escolha na raiz e delega a montagem a montarCasaDeControle(registro). O serviço depende de um contrato e pode receber um registro novo ou substituto. O parâmetro padrão não transforma a raiz em um registro substituível por configuração externa automaticamente: a substituição ocorre quando o chamador passa explicitamente outra implementação.

O registro padrão continua compartilhado por uma avaliação do módulo neste contexto. Injeção torna a dependência visível; não muda sozinha o ciclo de vida do objeto. Se o requisito não pede uma API de acesso compartilhado, criar uma instância com new na raiz e passá-la aos serviços é uma alternativa mais simples.

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/05-singleton
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