Pular para o conteúdo principal

Singleton

Descrição​

O Singleton garante que uma classe tenha uma única instância durante a vida da aplicação e fornece um ponto de acesso global a ela. A própria classe passa a ser responsável por controlar sua criação, em vez de deixar essa decisão para os clientes.

Para isso, o construtor é tornado privado (ou protegido), impedindo instanciações externas, e a classe expõe um método estático (por exemplo, getInstance()) que cria a instância na primeira chamada (inicialização preguiçosa) ou a cria antecipadamente (inicialização ansiosa) e devolve sempre o mesmo objeto nas chamadas seguintes.

Em ambientes concorrentes, a criação da instância precisa ser protegida (sincronização, double-checked locking, holder estático ou recursos da linguagem como enum em Java ou Lazy<T> em C#) para evitar que duas threads criem instâncias distintas.

Problemas que resolve​

  • Recursos que devem existir uma única vez no processo, como um pool de conexões, um cache em memória, um gerenciador de configuração ou um logger centralizado.
  • Necessidade de acesso controlado e consistente a um estado compartilhado, sem espalhar a criação do objeto por todo o código.
  • Evitar o custo de criar repetidamente objetos caros de inicializar.
  • Substituir variáveis globais por um ponto de acesso encapsulado, que pode adiar a criação até o primeiro uso.

Quando usar​

  • Quando deve haver exatamente uma instância de uma classe e ela precisa ser acessível a vários clientes a partir de um ponto conhecido.
  • Quando a instância única deve poder ser estendida por subclasses e os clientes devem usar a versão estendida sem alterar seu código.
  • Quando a inicialização é cara e deve ocorrer apenas sob demanda.

Quando evitar​

  • Quando o "único" é apenas uma conveniência: prefira injeção de dependência com escopo singleton gerenciado por um container.
  • Quando a classe guarda estado mutável compartilhado, pois isso cria acoplamento oculto e dificulta o raciocínio sobre o sistema.
  • Em código que precisa ser facilmente testável com mocks ou executado em paralelo nos testes.
  • Em sistemas distribuídos ou com múltiplos processos, em que "única instância" por processo não significa única instância no sistema.

Trade-offs​

GanhoCusto / Risco
Garante uma única instânciaEsconde dependências e cria acoplamento global
Ponto de acesso centralizadoDificulta testes unitários (estado persiste entre testes, difícil de mockar)
Inicialização preguiçosa possívelExige cuidado com concorrência na criação
Economia de recursos para objetos carosViola o princípio de responsabilidade única (gerencia o próprio ciclo de vida)
  • Acoplamento: clientes ficam presos à classe concreta ao chamar getInstance() diretamente; acessar via interface ou injeção reduz esse problema.
  • Testabilidade: o estado global pode vazar entre testes; costuma ser necessário um método de reset ou o uso de DI.
  • Concorrência: sincronização excessiva em getInstance() pode virar gargalo; prefira holder estático ou inicialização ansiosa quando possível.
  • Ciclo de vida: serialização, reflection e múltiplos class loaders podem quebrar a garantia de instância única.

Diagrama​

A classe Singleton mantém uma referência estática para a própria instância e um construtor privado; o Client não a instancia diretamente, apenas obtém o objeto por meio do método estático getInstance().

Exemplo​

Uma aplicação precisa de um único objeto de configuração carregado do disco uma vez e compartilhado por todos os módulos.

public final class AppConfig {
private final Properties props = new Properties();

private AppConfig() {
try (InputStream in = getClass().getResourceAsStream("/app.properties")) {
props.load(in);
} catch (IOException e) {
throw new IllegalStateException("Falha ao carregar configuracao", e);
}
}

// Holder: inicializacao preguicosa e thread-safe garantida pela JVM
private static class Holder {
private static final AppConfig INSTANCE = new AppConfig();
}

public static AppConfig getInstance() {
return Holder.INSTANCE;
}

public String get(String key) {
return props.getProperty(key);
}
}

// Uso
String url = AppConfig.getInstance().get("db.url");

Padrões relacionados​

  • Abstract Factory — fábricas concretas costumam ser implementadas como Singleton.
  • Builder — um builder compartilhado ou seu diretor pode ser exposto como Singleton.
  • Prototype — um registro de protótipos pode ser um Singleton.
  • Object Pool — o pool geralmente é único na aplicação e implementado como Singleton.
  • Facade — muitas vezes basta uma única fachada, que pode ser um Singleton.
  • Flyweight — a fábrica de flyweights normalmente é um Singleton; diferem porque o Flyweight admite várias instâncias compartilhadas.
  • State — objetos de estado sem dados próprios podem ser Singletons.
  • Null Object — o objeto nulo costuma ser exposto como instância única.

Referência​