Singleton
Singleton é um padrão de projeto que garante que uma classe tenha apenas uma instância e fornece um ponto global de acesso a ela. Ele é útil quando é necessário controlar o acesso a recursos compartilhados, como conexões de banco de dados, arquivos de configuração ou serviços de log.
Problema
Seção intitulada “Problema”Em muitos sistemas, existem recursos que deveriam ser únicos na aplicação inteira:
- Um arquivo de configuração central.
- Um gerenciador de conexão com o banco de dados.
- Um serviço de log que registra tudo que acontece no sistema.
Se cada parte do sistema puder criar sua própria instância dessas classes, alguns problemas surgem:
- Inconsistência de dados: duas instâncias de configuração podem ter valores diferentes carregados em momentos distintos.
- Uso excessivo de recursos: criar várias conexões de banco pode estourar o limite de conexões do servidor.
- Dificuldade de coordenação: se existem vários objetos que “mandam” na mesma coisa, fica difícil saber qual deles está com o estado correto.
- Dependências espalhadas: cada módulo sabe como criar sua própria instância, espalhando lógica de criação por todo o código.
Precisamos de uma forma de garantir que só exista uma instância de determinadas classes e que todos os pontos do sistema usem exatamente essa mesma instância.
Solução
Seção intitulada “Solução”O Singleton propõe três ideias principais:
-
Construtor privado
Impede que outras partes do código criem objetos diretamente comnew.
Assim, a própria classe controla quando e como a instância é criada. -
Atributo estático para armazenar a instância única
A classe mantém um atributo estático (por exemplo,instancia) que guarda o único objeto criado. -
Método estático de acesso (geralmente
getInstancia)- Se a instância ainda não existe, o método a cria e a armazena no atributo estático.
- Se a instância já existe, o método simplesmente a retorna.
- Todo o sistema passa a usar esse método como ponto único de acesso.
Com isso:
- A classe mesma controla sua própria quantidade de instâncias.
- O código cliente não se preocupa em “quantas” instâncias existem, apenas chama
MeuSingleton.getInstancia().
Analogia
Seção intitulada “Analogia”Pense em um serviço de emergência (como o 190, 192, 193, etc.):
- Você pode ligar de diferentes telefones: fixo, celular, telefone público.
- Pode discar números diferentes dependendo do tipo de emergência.
- Independentemente de como você ligue, a ligação vai parar em um serviço central de emergência que coordena o atendimento.
O importante é:
- Existem vários canais de acesso, mas o serviço que responde é único e centralizado.
- Você não quer que cada telefone crie seu próprio “mini serviço de emergência” local. Isso geraria confusão, dados incompletos, falta de coordenação, etc.
O Singleton funciona de forma parecida:
- O “serviço de emergência” é a instância única.
- Os “diferentes telefones” são as várias partes do sistema acessando o mesmo serviço.
- O número de telefone (por exemplo
getInstancia()) é o ponto global de acesso ao serviço.
Aplicabilidade
Seção intitulada “Aplicabilidade”Use Singleton quando:
- Deve existir apenas uma instância de uma classe na aplicação:
- Gerenciador de configurações da aplicação.
- Serviço de log central.
- Gerenciador de conexões com banco de dados.
- Gerenciador de fila de mensagens / eventos.
- Você precisa de um ponto global de acesso a um recurso compartilhado:
- Em uma aplicação desktop: preferência do usuário, tema atual, idioma atual.
- Em uma aplicação web (back-end): cache em memória, pool de conexões, provedor de autenticação.
- Você quer ter mais controle sobre a inicialização tardia (lazy):
- Só criar o objeto quando ele realmente for necessário, economizando recursos durante o startup.
Mas lembre-se: “usar Singleton” não significa apenas “quero algo global”; significa que você precisa garantir unicidade + acesso controlado.
Anti-padrão
Seção intitulada “Anti-padrão”Apesar de útil, Singleton é frequentemente considerado um anti-padrão quando usado de forma exagerada:
-
Dificulta testes unitários
- Singletons normalmente são globais e estáticos.
- Isso dificulta substituir o objeto por mocks ou stubs em testes.
- Testes começam a depender de estado global, o que gera testes “flaky” (que às vezes passam, às vezes falham).
Analogia: testar um sistema que usa um serviço de emergência “global” e real é como tentar simular um incêndio de verdade para ver se os bombeiros chegam. Seria melhor ter um “serviço falso para testes”.
-
Acoplamento global
- Qualquer classe pode chamar diretamente
Singleton.getInstancia(). - Seu código passa a depender diretamente do Singleton, o que torna mudanças futuras mais difíceis.
- É parecido com usar variáveis globais: fácil no começo, caro para manter depois.
Analogia: é como se todos na cidade tivessem o número pessoal do chefe do serviço de emergência e ligassem direto para o celular dele, ignorando a central. No início parece prático, mas vira caos rapidamente.
- Qualquer classe pode chamar diretamente
-
Uso desnecessário quando múltiplas instâncias não causariam problema
- Às vezes, ter mais de uma instância é totalmente aceitável ou até desejável (por exemplo, múltiplas conexões isoladas de banco em testes).
- Forçar Singleton só porque “fica mais simples chamar estático” acaba limitando o design.
-
Concorrência e estado compartilhado
- Em ambientes multithread, vários threads podem tentar acessar e alterar o mesmo Singleton.
- Sem o devido cuidado, isso leva a condições de corrida e bugs difíceis de reproduzir.
De forma geral: use Singleton com cuidado. Em muitos casos, injeção de dependência ou passar explicitamente as dependências por construtor/método é uma solução mais flexível.
Como implementar
Seção intitulada “Como implementar”Um passo a passo de uma implementação simples em Java:
-
Tornar o construtor privado
public class ConfiguracaoSistema {private ConfiguracaoSistema() {// construtor privado impede new ConfiguracaoSistema() fora da classe}} -
Criar um atributo estático privado para guardar a instância
public class ConfiguracaoSistema {private static ConfiguracaoSistema instancia;private ConfiguracaoSistema() {// inicialização}} -
Criar um método estático público que retorna a instância
public class ConfiguracaoSistema {private static ConfiguracaoSistema instancia;private ConfiguracaoSistema() {// carregaria configurações de um arquivo, por exemplo}public static ConfiguracaoSistema getInstancia() {if (instancia == null) {instancia = new ConfiguracaoSistema();}return instancia;}} -
Utilizar o Singleton no código cliente
public class ExemploUso {public static void main(String[] args) {ConfiguracaoSistema cfg1 = ConfiguracaoSistema.getInstancia();ConfiguracaoSistema cfg2 = ConfiguracaoSistema.getInstancia();System.out.println(cfg1 == cfg2); // true, mesma instância}} -
Considerar questões de concorrência (multithread)
A implementação acima não é segura em ambientes multithread. Em cenários concorrentes, usar:- Inicialização estática antecipada, ou
- Enum, ou
- Técnicas de sincronização (
synchronized, double-checked locking), se realmente necessário.
-
Controle explícito sobre a quantidade de instâncias
- Garante que só existe uma instância de uma classe (ex.: serviço de configuração).
-
Ponto global de acesso
- Qualquer parte do sistema obtém o mesmo objeto via
getInstancia(). - Facilita acesso a recursos compartilhados (log, config, cache).
- Qualquer parte do sistema obtém o mesmo objeto via
-
Inicialização tardia (lazy)
- Pode adiar a criação do objeto até ele realmente ser necessário.
- Útil quando a criação é custosa (ex.: carregar configurações de arquivo grande).
-
Substitui variáveis globais por uma solução mais estruturada
- Pelo menos, a criação e o acesso são encapsulados dentro da classe.
Contras
Seção intitulada “Contras”-
Dificulta testes
- Estado global e estático torna testes menos previsíveis.
- É difícil isolar testes e usar mocks.
-
Acoplamento forte ao Singleton
- Muitas classes passam a depender diretamente de uma implementação concreta.
- Trocar a implementação ou extrair uma interface depois é mais trabalhoso.
-
Risco de virar “lixeira global”
- É tentador colocar “tudo que precisa ser global” dentro do Singleton.
- Isso aumenta o tamanho e a responsabilidade da classe, violando o SRP (princípio da responsabilidade única).
-
Problemas em ambientes multithread
- Implementações ingênuas não são thread-safe.
- Correções para torná-las thread-safe podem introduzir complexidade (locks, sincronização).
Relações com outros padrões
Seção intitulada “Relações com outros padrões”-
Factory Method / Abstract Factory
- Podem ser usados dentro de um Singleton para criar objetos relacionados (por exemplo, um
FabricaDeRepositoriosSingleton). - O Singleton controla “quantas fábricas existem”, enquanto a fábrica controla “como objetos são criados”.
- Podem ser usados dentro de um Singleton para criar objetos relacionados (por exemplo, um
-
Dependency Injection (DI)
- Muitas vezes, DI é uma alternativa melhor a Singletons.
- Em vez de chamar
MeuSingleton.getInstancia(), você recebe a dependência por construtor ou injeção. - Isso reduz acoplamento, facilita testes e permite trocar implementações.
-
Facade
- Um Facade pode ser implementado como Singleton, quando você quer uma única fachada para um subsistema.
- Porém, Facade é sobre expor uma interface simplificada; Singleton é sobre unicidade da instância.
-
State / Strategy
- Às vezes, cada estado/estratégia pode ser um Singleton se o estado não guarda dados específicos por contexto (por exemplo, estados imutáveis únicos).
Implementações alternativas
Seção intitulada “Implementações alternativas”Lazy Singleton
Seção intitulada “Lazy Singleton”No lazy singleton, a instância é criada apenas na primeira vez que é solicitada.
public class ServicoEmergencia { private static ServicoEmergencia instancia;
private ServicoEmergencia() { // configura o serviço (por exemplo, carrega regras, contatos, etc.) }
public static ServicoEmergencia getInstancia() { if (instancia == null) { instancia = new ServicoEmergencia(); } return instancia; }
public void atenderLigacao(String tipoEmergencia) { System.out.println("Atendendo emergência: " + tipoEmergencia); }}Uso:
public class AppUniversitaria { public static void main(String[] args) { ServicoEmergencia servico1 = ServicoEmergencia.getInstancia(); ServicoEmergencia servico2 = ServicoEmergencia.getInstancia();
servico1.atenderLigacao("incendio no laboratorio"); System.out.println(servico1 == servico2); // true }}Singleton com enum
Seção intitulada “Singleton com enum”Em Java, uma forma simples e segura de implementar Singleton é usando enum:
public enum CentralEmergencia { INSTANCIA; // única instância
public void atenderLigacao(String tipoEmergencia) { System.out.println("Central de Emergência atendendo: " + tipoEmergencia); }}Uso:
public class AppUniversitaria { public static void main(String[] args) { CentralEmergencia central1 = CentralEmergencia.INSTANCIA; CentralEmergencia central2 = CentralEmergencia.INSTANCIA;
central1.atenderLigacao("acidente no estacionamento"); System.out.println(central1 == central2); // true }}Essa abordagem com enum é thread-safe, simples e lida bem com serialização, sendo recomendada pelo próprio Joshua Bloch (autor de Effective Java).
Exemplo em Java
Seção intitulada “Exemplo em Java”Abaixo um exemplo completo usando o tema de configuração de sistema em um projeto de Engenharia de Software (por exemplo, um sistema web usado pela universidade):
public class ConfiguracaoSistema { private static ConfiguracaoSistema instancia;
private String urlBanco; private String usuarioBanco; private String idiomaInterface;
private ConfiguracaoSistema() { // Em um cenário real, leria de um arquivo .properties, .yaml, etc. this.urlBanco = "jdbc:mysql://localhost:3306/universidade"; this.usuarioBanco = "aluno_dev"; this.idiomaInterface = "pt-BR"; }
public static ConfiguracaoSistema getInstancia() { if (instancia == null) { instancia = new ConfiguracaoSistema(); } return instancia; }
public String getUrlBanco() { return urlBanco; }
public String getUsuarioBanco() { return usuarioBanco; }
public String getIdiomaInterface() { return idiomaInterface; }}Uso em outra parte do sistema:
public class RepositorioAluno { public void conectar() { ConfiguracaoSistema cfg = ConfiguracaoSistema.getInstancia(); System.out.println("Conectando ao banco: " + cfg.getUrlBanco()); // aqui iria o código real de conexão usando a url e o usuário }}public class App { public static void main(String[] args) { RepositorioAluno repo1 = new RepositorioAluno(); RepositorioAluno repo2 = new RepositorioAluno();
repo1.conectar(); repo2.conectar();
// Ambos usam a mesma instância de ConfiguracaoSistema, // assim como diferentes telefones usam o mesmo serviço de emergência central. }}