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.

Padrões de projeto de software

Os padrões de projeto de software ou padrões de desenho de software, também muito conhecido pelo termo original em inglês: Design Patterns, descrevem soluções para problemas recorrentes no desenvolvimento de sistemas de software orientados a objetos. Um padrão de projeto estabelece um nome e define o problema, a solução, quando aplicar esta solução e suas conseqüências.

Os padrões de projeto visam facilitar a reutilização de soluções de desenho - isto é, soluções na fase de projeto do software, sem considerar reutilização de código. Também acarretam um vocabulário comum de desenho, facilitando comunicação, documentação e aprendizado dos sistemas de software.

Um Padrão de Projeto pode ser definido como “a solução para um problema em um contexto”. Isto significa que os padrões de projeto nos oferecem soluções prontas para utilizarmos em determinados problemas que podemos enfrentar quando desenvolvemos um software orientado a objetos.

Para realizar a descrição de um padrão de projeto devemos especificar o seu nome, que o identifica e descreve; o contexto, ou seja, a situação que está gerando se está na qual se deve aplicar o padrão; o problema, que é a meta que está se tentando atingir neste contexto; a solução proposta pelo padrão para o problema no contexto especificado e as conseqüências da aplicação do padrão, podendo ser elas boas ou ruins. Para compreender melhor esse conceito, veja o exemplo abaixo, que não pode ser considerado um padrão de projeto, servindo apenas para ajudar a compreender o modo como um padrão de projeto deve ser definido.

  • Nome: Chegar à reunião!
  • Problema: Como chegar no horário à reunião?
  • Contexto: Você está trancado dentro de casa!
  • Solução: Pule a janela, entre no carro e corra até o trabalho.
  • Conseqüências: Você deve achar a chave ou pular a janela para entrar em casa.

Podemos classificar os padrões de projeto de várias maneiras, porém o mais comum é classificá-los de acordo com os tipos de problemas que eles resolvem. De acordo com esse critério, os padrões podem ser:

  • Criação: resolvem os problemas da criação de objetos;
  • Estruturais: que lidam com os problemas de relacionamentos entre objetos;
  • Comportamentais: que lidam com os problemas de atribuição de responsabilidades aos objetos.

Os padrões de projeto solucionam muitos dos problemas que os projetistas enfrentam no dia a dia, e de diversas maneiras diferentes. Nesta série de post tentarei mostrar alguns dos padrões mais comuns, explicando o contexto, o problema, a solução e suas conseqüências com exemplos simples e acessíveis. Todos os exemplos estarão escritos na linguagem Java, mas são aplicáveis em qualquer linguagem orientada a objetos como .Net, Delphi, C++ ou outras.