O padrão de projeto Singleton é um padrão que tem como objetivo garantir que apenas uma única instância da classe alvo é criada. É um dos padrões de projetos mais utilizados. Outro fator interessante do uso do Singleton é quando precisamos ter um único ponto de acesso global para a instância de classe. Por exemplo, alguma classe que possui dados de configuração que são únicos para toda a aplicação.
Existem algumas maneiras de implementar o Singleton. A maneira mais comum é a seguinte:
public class SingletonUm { private static final SingletonUm instance = new SingletonUm(); private SingletonUm() { } public static SingletonUm getInstance() { return instance; } }
O construtor privado (linha 3) impossibilita a criação de novas instâncias, pois, por ser privado, só pode ser referenciado dentro da mesma classe. O método getInstance() (linha 5) fornece o acesso à instância criada na linha 2. Ele é estático para poder ser acessado sem ter a instância (já que o construtor é privado e queremos que a instância seja única). Então para obtermos acesso à classe é só chamar: SingletonUm.getInstance();
O problema dessa abordagem é que se não formos utilizar a instância ou se formos utilizar ela posteriormente, a aplicação terá um objeto desnecessário em memória. Então para termos uma instanciação tardia, devemos adotar outra abordagem. Outra implementação bastante comum é a seguinte:
public class SingletonDois { private static SingletonDois instance; private SingletonDois() { } public static SingletonDois getInstance() { if (instance == null) { instance = new SingletonDois(); } return instance; } }
Com essa verificação a instância só será criada após chamado o método getInstance(). Para torná-lo thread-safe, é só colocar o modificador synchronized no método. O problema dessa inclusão é o gargalo que teremos ao transformar o método em synchronized, além de outros possíveis problemas. Nesse link tem maiores descrições desses problemas. Para resolver, o Bill Pugh (um dos autores do artigo citado), desenvolveu a seguinte solução:
public class SingletonBillPugh { private SingletonBillPugh() { } private static class SingletonHolder { public static final SingletonBillPugh instance = new SingletonBillPugh(); } public static SingletonBillPugh getInstance() { return SingletonHolder.instance; } }
Baseado no modelo de inicialização do Java, o código da linha 05 só é chamado após a invocação do método getInstance() (linha 07). Dessa maneira garantimos instanciação tardia e o código ser thread-safe.
Outra maneira mais elegante de se fazer Singleton é o citado no livro Effective Java:
public enum SingletonEnum { INSTANCE; // Operações do Enum aqui! }
O enum não pode ser instanciado, só tem uma instância única, que é o nosso Singleton. O único problema dessa abordagem é que temos alguns limitantes no enum, como a impossibilidade de se fazer herança e não temos instanciação tardia.
As dúvidas agora permanecem quando utilizar uma solução ou outra. Minha recomendação é a seguinte. Se não é necessário ter inicialização tardia, utilizar a solução SingletonEnum, desde que a classe alvo não tenha problemas com os limitantes do enum, caso contrário, utilizar a solução SingletonUm. Se é necessário ter instanciação tardia utilizar a solução SingletonBillPugh, desde que não seja necessário realizar tratamento de erros ao chamar o getInstance(), caso contrário, utizar a solução SingletonDois.