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
01 · Entenda a escolha
Os fundamentos do padrão.
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: contratoRegistroe dados de ordens.src/registro.ts:RegistroEmMemoria, sem restrição de construtor; aceitanewpara 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.tsesrc/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ótipomodulerepresentam 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.
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}`);
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 Registro, RegistroEmMemoria. RegistroEmMemoria realiza Registro. Membros exibidos são um recorte; construtores e demais membros podem estar omitidos.
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
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.
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 solutionBaixar este projeto completo