Singleton
Garante que uma classe tenha exatamente uma instância durante toda a vida da aplicação e fornece um ponto de acesso global a essa instância.
Intenção
Assegurar que uma classe seja instanciada uma única vez e prover um ponto de acesso global a essa instância, eliminando a necessidade de passar a referência por toda a aplicação.
O Singleton é um dos 23 padrões do catálogo original de Gamma, Helm, Johnson e Vlissides — o livro Design Patterns: Elements of Reusable Object-Oriented Software (1994), carinhosamente chamado de "Gang of Four" (GoF). Pertence à categoria de padrões de criação, que lidam com mecanismos de instanciação de objetos.
Problema
Algumas responsabilidades em um sistema precisam existir em um único lugar: o pool de conexões com o banco de dados, o logger central, o cache em memória, as configurações carregadas de um arquivo, o barramento de eventos. Criar múltiplas instâncias dessas estruturas causa inconsistências: cada instância mantém seu próprio estado, os logs são fragmentados, o cache é duplicado.
A solução ingênua é usar uma variável global. O problema: variáveis globais permitem que qualquer parte do código sobrescreva o valor, não garantem que a inicialização ocorra uma única vez e tornam as dependências implícitas, dificultando testes e manutenção.
O Singleton resolve isso encapsulando a lógica de controle de instância dentro da própria classe, tornando impossível (ou ao menos explicitamente difícil) criar uma segunda instância acidentalmente.
Solução
A implementação canônica do Singleton tem três elementos obrigatórios:
-
Construtor privado (ou protegido): impede que código externo
instancie a classe com
new. -
Campo estático privado: armazena a única instância da classe
(o campo começa
nullouundefined). -
Método estático público (
getInstance()): verifica se a instância existe; se não existir, cria e armazena; sempre retorna a mesma referência.
Ao chamar MinhaClasse.getInstance() várias vezes, todas as chamadas
recebem o mesmo objeto — não há cópias, não há estado duplicado.
Estrutura
O diagrama abaixo, em notação UML simplificada, mostra a classe Singleton com seus membros essenciais:
┌───────────────────────────────────────────┐
│ <<Singleton>> │
│ ConfigManager │
├───────────────────────────────────────────┤
│ - instance: ConfigManager (estático) │
│ - dados: Record<string, string> │
├───────────────────────────────────────────┤
│ - constructor() (privado) │
│ + getInstance(): ConfigManager (estático)│
│ + get(chave: string): string | undefined │
│ + set(chave: string, valor: string): void │
└───────────────────────────────────────────┘
Fluxo de uso:
Código cliente A Código cliente B
│ │
│ ConfigManager.getInstance() │ ConfigManager.getInstance()
│ ─────────────────────────────►│
│ │
│ ┌─────────────────────┤
│ │ instância já existe│
│ │ → retorna a mesma │
│ └─────────────────────┤
│◄──────────────────────────────│
│ mesma referência │
Exemplos de código
Exemplo 1 — Implementação ingênua (não thread-safe)
Esta é a forma mais simples do Singleton. Em JavaScript e PHP (single-threaded na maioria dos casos), funciona bem para entender o padrão. Mais adiante discutimos as limitações.
class Logger {
// Campo estático privado que guarda a única instância.
private static instance: Logger | null = null;
// Construtor privado — impede `new Logger()` fora da classe.
private constructor(private readonly prefix: string = "[LOG]") {}
// Ponto de acesso global: cria na primeira chamada, reutiliza nas demais.
public static getInstance(): Logger {
if (Logger.instance === null) {
Logger.instance = new Logger();
}
return Logger.instance;
}
public info(mensagem: string): void {
console.log(`${this.prefix} [INFO] ${mensagem}`);
}
public erro(mensagem: string): void {
console.error(`${this.prefix} [ERRO] ${mensagem}`);
}
}
// ── Uso ──────────────────────────────────────────────────────
const logA = Logger.getInstance();
const logB = Logger.getInstance();
console.log(logA === logB); // true — mesma referência
logA.info("Aplicação iniciada");
logB.erro("Algo deu errado");
// Ambas as chamadas escrevem no mesmo logger.
<?php
class Logger
{
// Campo estático privado que guarda a única instância.
// Usa `self` (não `static`) porque o construtor é private,
// o que inviabiliza herança — LSB seria incoerente aqui.
private static ?self $instance = null;
// Construtor privado — impede `new Logger()` fora da classe.
private function __construct(
private readonly string $prefix = '[LOG]'
) {}
// Impede clonagem da instância.
private function __clone(): void {}
// Ponto de acesso global: cria na primeira chamada, reutiliza nas demais.
public static function getInstance(): self
{
if (self::$instance === null) {
self::$instance = new self();
}
return self::$instance;
}
public function info(string $mensagem): void
{
echo "{$this->prefix} [INFO] {$mensagem}" . PHP_EOL;
}
public function erro(string $mensagem): void
{
echo "{$this->prefix} [ERRO] {$mensagem}" . PHP_EOL;
}
}
// ── Uso ──────────────────────────────────────────────────────
$logA = Logger::getInstance();
$logB = Logger::getInstance();
var_dump($logA === $logB); // bool(true) — mesma referência
$logA->info('Aplicação iniciada');
$logB->erro('Algo deu errado');
Exemplo 2 — Singleton com inicialização lazy e tipagem forte
Em aplicações reais, o Singleton costuma encapsular um recurso inicializado com parâmetros (como uma string de conexão). O padrão abaixo aceita opções na primeira chamada e as ignora nas chamadas subsequentes.
interface ConfigOpcoes {
readonly arquivo: string;
readonly ambiente: "dev" | "staging" | "prod";
}
class ConfigManager {
private static instance: ConfigManager | null = null;
private readonly dados: Map<string, string> = new Map();
private constructor(opcoes: ConfigOpcoes) {
// Simula o carregamento de um arquivo de configuração.
this.dados.set("env", opcoes.ambiente);
this.dados.set("arquivo", opcoes.arquivo);
console.log(`[Config] Carregado de "${opcoes.arquivo}" (${opcoes.ambiente})`);
}
// Na primeira chamada, `opcoes` é obrigatório.
// Nas chamadas subsequentes, o argumento é ignorado.
public static getInstance(opcoes?: ConfigOpcoes): ConfigManager {
if (ConfigManager.instance === null) {
if (!opcoes) {
throw new Error("ConfigManager: opcoes obrigatórias na primeira inicialização.");
}
ConfigManager.instance = new ConfigManager(opcoes);
}
return ConfigManager.instance;
}
public get(chave: string): string | undefined {
return this.dados.get(chave);
}
public set(chave: string, valor: string): void {
this.dados.set(chave, valor);
}
/** Apenas para testes — permite resetar a instância entre casos de teste. */
public static resetParaTestes(): void {
ConfigManager.instance = null;
}
}
// ── Uso ──────────────────────────────────────────────────────
const cfg = ConfigManager.getInstance({
arquivo: ".env.prod",
ambiente: "prod",
});
console.log(cfg.get("env")); // "prod"
// Segunda chamada sem opções — retorna a instância existente.
const cfg2 = ConfigManager.getInstance();
console.log(cfg === cfg2); // true
<?php
class ConfigManager
{
// Usa `self` (não `static`): construtor private impede subclasses,
// então late static binding não agrega valor e causaria confusão.
private static ?self $instance = null;
/** @var array<string, string> */
private array $dados = [];
private function __construct(
private readonly string $arquivo,
private readonly string $ambiente
) {
// Simula o carregamento de um arquivo de configuração.
$this->dados['env'] = $ambiente;
$this->dados['arquivo'] = $arquivo;
echo "[Config] Carregado de \"{$arquivo}\" ({$ambiente})" . PHP_EOL;
}
private function __clone(): void {}
/**
* Na primeira chamada, $arquivo e $ambiente são obrigatórios.
* Nas chamadas subsequentes, os argumentos são ignorados.
*
* Nota PHP: em share-nothing (FPM padrão), a instância é recriada
* a cada request — não há estado entre requisições, ao contrário
* do Node.js onde o módulo persiste em memória.
*/
public static function getInstance(
string $arquivo = '',
string $ambiente = ''
): self {
if (self::$instance === null) {
if ($arquivo === '') {
throw new \RuntimeException(
'ConfigManager: $arquivo obrigatório na primeira inicialização.'
);
}
self::$instance = new self($arquivo, $ambiente);
}
return self::$instance;
}
public function get(string $chave): ?string
{
return $this->dados[$chave] ?? null;
}
public function set(string $chave, string $valor): void
{
$this->dados[$chave] = $valor;
}
/** Apenas para testes — permite resetar a instância entre casos de teste. */
public static function resetParaTestes(): void
{
self::$instance = null;
}
}
// ── Uso ──────────────────────────────────────────────────────
$cfg = ConfigManager::getInstance('.env.prod', 'prod');
echo $cfg->get('env') . PHP_EOL; // prod
// Segunda chamada sem argumentos — retorna a instância existente.
$cfg2 = ConfigManager::getInstance();
var_dump($cfg === $cfg2); // bool(true)
Quando usar
- Recurso compartilhado com estado único: pool de conexões com banco de dados, cliente HTTP com configuração centralizada, cache em memória.
- Logger / sistema de auditoria: toda a aplicação deve escrever no mesmo destino, sem fragmentação de saída.
- Configuração carregada uma vez: variáveis de ambiente, feature flags, parâmetros de aplicação carregados na inicialização.
- Barramento de eventos (event bus) simples: um único canal que distribui eventos entre subsistemas.
Quando evitar
-
Objetos de domínio:
Pedido,Usuario,Produtonunca devem ser Singletons — cada entidade tem identidade própria. - Services em frameworks com DI: NestJS, Spring, Laravel e similares já gerenciam o ciclo de vida dos serviços. Use o escopo do container (singleton scope no DI) em vez de implementar o padrão manualmente.
-
Quando a testabilidade importa: Singletons com estado
persistente entre testes quebram o isolamento. Se não houver um método de
reset (como o
resetParaTestes()do exemplo), prefira injeção de dependência.
Prós e contras
Prós
- Garante uma única instância — sem estado duplicado nem inconsistência.
- Ponto de acesso global consistente, sem necessidade de passar referências por parâmetros.
- Inicialização lazy (a instância só é criada quando solicitada pela primeira vez), economizando recursos se o Singleton nunca for usado.
- Fácil de localizar no código: basta buscar por
getInstance().
Contras
- Viola o Princípio da Responsabilidade Única: a classe gerencia tanto sua lógica de negócio quanto seu ciclo de vida.
- Dificulta testes unitários — é preciso um mecanismo de reset ou usar técnicas de mock avançadas.
- Introduz acoplamento global implícito: qualquer parte do código pode acessar e modificar o estado do Singleton.
- Problemas de concorrência em ambientes multi-thread (Java, C#, Go) — a verificação check-then-act não é atômica sem sincronização.
- Dificulta a detecção de dependências: ao contrário da injeção de dependência, o acoplamento com o Singleton não aparece na assinatura do construtor.
Armadilhas comuns
1. Estado global disfarçado
O Singleton é, na prática, uma variável global orientada a objetos. Qualquer
parte do sistema pode chamar Singleton.getInstance() e alterar
seu estado. Isso cria dependências ocultas que não aparecem nas assinaturas
dos métodos e que só se revelam em tempo de execução — especialmente doloroso
em projetos grandes com múltiplas equipes.
2. Dificulta testes e mocks
Atenção: Testes unitários que dependem de um Singleton com estado podem interferir entre si se executados na mesma instância de processo. O estado do Singleton do Teste A "vaza" para o Teste B.
A solução clássica é adicionar um método resetParaTestes() (como
no Exemplo 2) ou, melhor ainda, refatorar o código para aceitar a dependência
via injeção (construtor ou parâmetro) — aí nos testes você passa um objeto
fake sem precisar do Singleton.
3. Problemas de concorrência (multi-thread) e ciclo de vida
Em TypeScript/Node.js e PHP padrão (sem pthreads), a execução é single-threaded
e a condição de corrida não ocorre. Mas em Java, C# ou Go, dois threads podem
passar pela verificação if (instance == null) simultaneamente e
criar duas instâncias. A solução é usar sincronização (synchronized,
lock, ou inicialização estática garantida pela linguagem).
Atenção — PHP vs Node.js: há uma diferença de ciclo de vida que confunde muita gente. O PHP padrão segue o modelo share-nothing por request: cada requisição HTTP inicia um processo (ou worker) do zero, executa o script e descarta tudo ao final. Isso significa que o Singleton não persiste entre requisições — ele é recriado a cada request. Não há vazamento de estado entre usuários, mas também não há ganho de inicialização entre chamadas. Já em TypeScript/Node.js, o processo é long-lived: o mesmo módulo é carregado uma vez e permanece em memória enquanto o servidor estiver de pé. O Singleton criado na primeira requisição sobrevive e é reutilizado por todas as requisições subsequentes — o que traz os benefícios de cache e os riscos de estado compartilhado entre usuários concorrentes.
4. Herança e subclasses
Singleton e herança não combinam bem. Se LoggerFilho extends Logger,
o campo estático instance pode ser compartilhado entre pai e filho
dependendo da linguagem, gerando comportamentos inesperados. Com construtor
private (como nos exemplos acima), a herança é simplesmente
impossível — o que torna desnecessário, e enganoso, o uso de
static::$instance (late static binding) em PHP. Se você precisar
de um Singleton herdável, troque o construtor para protected e aí
sim adote static::$instance com new static() — mas
esteja ciente de que a complexidade aumenta rapidamente.
5. Serialização e desserialização
Em PHP, se o objeto for serializado e desserializado (ex.: via unserialize()),
uma nova instância é criada, quebrando a garantia do Singleton. A solução é
implementar __wakeup() lançando uma exceção para proibir a
desserialização.
Padrões relacionados
O Singleton frequentemente aparece em conjunto ou em contraste com outros padrões:
- Factory Method
- Abstract Factory
- Monostate — variação (em breve)
- Flyweight
- Facade
O Monostate é uma alternativa ao Singleton: em vez de controlar a instância, todos os campos são estáticos, de modo que múltiplos objetos compartilham o mesmo estado — mais fácil de testar, mas igualmente acoplado. O Flyweight também controla a criação de instâncias, mas para compartilhar objetos imutáveis em grande número. O Facade frequentemente é implementado como Singleton para fornecer um ponto de acesso simplificado a um subsistema.