sexta-feira, 25 de janeiro de 2008

O padrão Singleton

O Singleton é um dos padrões de projeto mais simples existentes. É um padrão de projeto de software (do inglês Design Pattern). Este padrão garante a existência de apenas uma instância de uma classe, mantendo um ponto global de acesso ao seu objeto.

Nota linguística: O termo vem do significado em inglês quando se resta apenas uma carta nas mãos, num jogo de baralho.

O GoF nos diz que para uma classe ser um singleton, deve-se garantir que haverá apenas uma instância na aplicação e que deve-se fornecer um ponto de acesso à mesma. Mas como garantir que haverá apenas uma instância? É de conhecimento comum que, para criar uma instância de uma classe, devemos chamar o seu construtor. Assim, para resolvermos o problema, devemos restringir o acesso ao construtor, tornando-o um método privado. Em seguida, deve-se utilizar um método público que faça o controle da instanciação, de modo que ela só possa ser feita uma vez. A Figura 1 mostra o diagrama de classes do singleton:

Figura 1: Diagrama de Classes do Singleton.

A implementação mais comum do singleton pode ser vista no código apresentado na Listagem 1 e comumente é chamada de singleton com lazy load.

public class Singleton {
private static Singleton instance;

private Singleton() { }

public static Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}

Implementação do Singleton.

O diagrama de classes da Figura 1 e o código da Listagem 1 mostram apenas o necessário para a instanciação do singleton. Obviamente, a classe deve conter outros atributos e métodos.
A Figura 2 mostra um diagrama de seqüências que representa o processo de criação de um singleton. Quando um objeto chama o singleton pela primeira vez, é realizada a instanciação da classe. Os demais acessos irão utilizar a instância já criada.

Figura 2: Diagrama de Seqüências de um Singleton.

O código da Listagem 1 pode ser problemático em ambientes multi-threaded, ou seja, ele não é uma solução thread-safe. Se uma thread chamar o método getInstance() e for interrompida antes de realizar a instanciação, uma outra thread poderá chamar o método e realizar a instanciação. Neste caso, duas instâncias serão construídas, o que fere os requisitos do singleton.


Uma solução para este problema seria utilizar o atributo synchronized em getInstance(), como visto na Listagem 2 , para que uma outra thread não possa acessá-lo até que a thread que o acessou pela primeira vez tenha terminado de fazê-lo.

public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) instance = new Singleton();
return instance;
}
}
Implementação thread-safe com synchronized.

O problema com o synchronized é que a sincronização é bastante custosa. Estima-se que métodos sincronizados sejam cerca de cem vezes mais lentos que métodos não sincronizados.

Uma alternativa simples, rápida e thread-safe é a instanciação do singleton assim que ele for declarado. Veja a Listagem 3 .

public class Singleton {
private static Singleton instance = new Singleton();
private Singleton() { }
public static Singleton getInstance() { return instance; }
}
Implementação thread-safe com instanciação estática.

3. Utilizações comuns

Muitos projetos necessitam que algumas classes tenham apenas uma instância. Por exemplo, em uma aplicação que precisa de uma infraestrutura de log de dados, pode-se implementar uma classe no padrão singleton. Desta forma existe apenas um objeto responsável pelo log em toda a aplicação que é acessível unicamente através da classe singleton. Outra utilização comum é utilizar o padrão singleton sobre a fábrica de conexões com o banco.

3. Conclusões
Além de ser um dos padrões mais simples, o singleton é também um dos mais criticados e mal usados. Uma situação em que realmente é necessário que exista apenas uma instância de uma classe é difícil e o seu uso pode levar a muitos problemas. O uso abusivo de singletons leva a soluções onde a dependência entre objetos é muito forte e a testabilidade é fraca.

Adicionalmente, os singletons são difíceis de escalar, pois é difícil garantir uma única instância de um singleton em um cluster, onde existem várias JVMs. Um outro problema é que os singletons dificultam o hot redeploy por permanecerem em cache, bloqueando mudanças de configuração. Por esses e outros problemas, deve-se ter cuidado com o uso abusivo de singletons.

Um comentário:

Carlos José Vaz disse...

Muito esclarecedor esse artigo, muito bom.