IDE: NetBeans 5.5 (JDK 6 Update 1)
Olá pessoal! Para quem não conhece, apresento hoje um padrão de projeto muito utilizado entre os desenvolvedores de sistemas: o padrão Singleton. Um padrão de projeto é um template que define um comportamento específico (ou alguma técnica) para determinado problema, o tornando mais flexível e reutilizável. O design pattern Singleton (um ponto de acesso global a um objeto) nos garante que apenas uma e somente uma instância de uma classe será criada.
Quando utilizar?
Utilizamos este padrão quando desejamos ter certeza de que queremos restringir a criação de um objeto a apenas uma instância. Isso é bastante útil quando precisamos de apenas um objeto para determinar as coordenadas do sistema fazendo com que o mesmo trabalhe mais eficientemente. Utilizar uma classe Singleton apenas para armazenar variáveis globais é considerado um princípio anti-padrão de projetos. Mas por que então não utilizamos uma variável ou método estático? Pois variáveis estáticas não nos dão as possibilidades que um Singleton nos oferece.
Podemos por exemplo, configurar nossa classe para armazenar não só uma, mas duas, três ou mais instâncias de um determinado objeto, nos dando a possibilidade de escolha de quantidade de instâncias possíveis em nosso sistema. Podemos também criar nosso objeto apenas quando necessário evitando sua inicialização quando não utilizamos o mesmo, diminuindo assim o consumo de memória da aplicação. Outra opção que este padrão nos dá, é a possibilidade de herança e implementação de novos métodos, possibilidades que uma simples variável ou classe estática não nos dariam. Imagine que você venda um sistema onde cada conexão com o banco de dados custará um preço X. Você poderia aumentar o número de instâncias permitidas após o pagamento de uma nova conexão utilizando um simples Singleton. As situações são diversas. O importante é conhecer o pattern e entende-lo perfeitamente. Entender o contexto de uso é essencial.
Suponha que você está construindo um aplicativo e precisa carregar e manipular as configurações globais do mesmo. Duas instâncias deste mesmo objeto de configuração seriam desnecessárias, pois, como o próprio nome diz, estas configurações são globais e únicas do aplicativo como um todo. Para demonstrarmos esta regra sendo executada, criaremos uma classe chamada SingletonTest que definirá nosso template de software para que este nos obrigue a criar no máximo uma instância da própria classe.
A implementação de Singleton é composta de três passos básicos: bloquear o acesso a seu construtor, assegurar que apenas uma instância da mesma seja referenciada e impedir a clonagem da própria classe. Estes três passos nos asseguram que apenas uma referência de nossa classe será criada.
O primeiro passo é entender como bloquear nosso construtor para que o mesmo não possa ser instanciado. Observe o código abaixo:
public class SingletonTest {
private SingletonTest() {
// TODO: Código do construtor aqui.
}
}
No código acima, declaramos uma classe SingletonTest e um construtor vazio para a mesma. Note que definimos o método de acesso a este construtor como private (isto é, só podemos instanciar um Singleton dentro da própria classe [note que um construtor protected nos dá a possibilidade de herança do pattern]). É aqui que esta a grande diferença dos demais construtores comumente usados (em sua maioria, publics). Como definimos nosso construtor como privado, o código cliente nunca instanciará esta classe em nenhum lugar do programa. Qualquer tentativa irá gerar um erro de compilação. Mas então quer dizer que criamos uma classe impossível de se instanciar? Qual o sentido disso? O sentido é que assim, temos certeza que nenhuma instância desta classe será criada diretamente com o operador new. Mas então como obter o objeto? Para isto, criaremos um método estático chamado getInstance que retornará uma referência para um objeto SingletonTest. Sendo este método estático, não precisaremos de um novo objeto para chamar o mesmo.
public class SingletonTest {
private static SingletonTest referencia;
private SingletonTest() {
// TODO: Código do construtor aqui.
}
public static SingletonTest getInstance() {
if(referencia == null)
referencia = new SingletonTest();
return referencia;
}
}
Modificamos nossa classe SingletonTest adicionando uma variável de instância chamada referencia. Esta variável será a responsável por armazenar a referência do único objeto SingletonTest utilizado em nosso exemplo. Logo em seguida adicionamos o método getInstance que por sua vez, checa se o campo referencia está vazio ou já está instanciado com algum objeto SingletonTest. Se esta condição for verdade, isto é, referencia for igual à null, podemos então criar um novo SingletonTest e retorná-lo ao seu chamador. Se a condição for falsa, concluímos que referencia já foi inicializada como um SingletonTest e retornamos então o objeto já referenciado atualmente pelo campo. Assim asseguramos que o mesmo objeto sempre seja repassado a demais variáveis de referencia que chamam o método getInstance.
Mas e se estivermos trabalhando com threads? Suponha agora que tenhamos duas threads concorrentes. Suponha que a thread A tenha que modificar o valor da referência através de uma função antes da thread B, para que este possa funcionar corretamente (thread B então depende de thread A). Como faremos para sincronizar estas duas threads e impedir que thread A seja concorrente de thread B no mesmo método? Como saber se a thread B será chamada apenas quando thread A terminar sua função?
Para obtermos este comportamento, utilizamos a palavra reservada synchronized que “trancará” o método utilizado, tornando assim impossível a manipulação do mesmo objeto por threads diferentes.
public static synchronized SingletonTest getInstance() {
if(referencia == null)
referencia = new SingletonTest();
return referencia;
}
Para terminarmos nosso padrão, precisamos nos prevenir de mais uma situação: a clonagem de métodos e classes com o método clone. O método clone (herdado da classe Object) é uma implementação da interface Cloneable, que nos dá o poder de implementar nossa própria versão do método de acordo com as nossas necessidades. Esta interface é bastante utilizada para implementação de clonagens Shallow Copy e Deep Copy (não explicarei estas aqui para não perdermos o foco do artigo). A maioria dos códigos e artigos encontrados na Internet, falham justamente neste ponto. Observe o código abaixo:
public class Clone {
public static void main(String args[]) throws Exception {
SingletonTest teste = SingletonTest.getInstance();
SingletonTest clone = (SingletonObject)teste.clone();
}
}
Note que criamos primeiramente um objeto teste para armazenar um objeto SingletonTest e logo em seguida clonamos o mesmo para a variável clone. Como o método clone (herdado de java.lang.Object) retorna um objeto Object, precisamos fazer um cast explícito ((SingletonObject)) para obtermos uma instância do mesmo tipo do antigo objeto. Desta forma a clonagem ocorre perfeitamente.
Para nos livrarmos deste “buraco” e impedir a clonagem de nossa classe, sobrescrevemos então este (clone é protected por default) para que o mesmo “jogue” uma exceção caso haja uma tentativa de clonagem. Nosso método clone sobrescrito se torna então:
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
Note que, no código acima, utilizamos a exceção CloneNotSupportedException que lança uma exceção a cada tentativa de clonagem do objeto atual.
Se você quiser implementar herança em sua classe Singleton, terá que modificar seu construtor de private para protected, permitindo assim que o construtor seja chamado na classe filha. Mas se por um acaso seu Singleton for composto de apenas uma classe, podemos utilizar o comando final que impede a classe de ser herdada por outra classe. Assim, o compilador pode ajustar otimizações já tendo certeza de que nossa classe sempre será a classe concreta. Apenas modifique a declaração da classe para:
public final class SingletonTest {
...
Com todas as precauções básicas, e com um Singleton 100% Singleton, vamos conferir o código de exemplo.
public final class SingletonTest {
private static SingletonTest referencia;
private SingletonTest() {
// TODO: Código do construtor aqui.
}
public static synchronized SingletonTest getInstance() {
if(referencia == null)
referencia = new SingletonTest();
return referencia;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
Vamos conferir outra forma de inicialização de um Singleton. Esta forma utiliza uma inner class que armazenará um campo chamado _Instance (trocamos o nome referencia) e a criação do objeto. Utilizei o código acima pois o mesmo oferece melhor entendimento do negócio. Particularmente, prefiro utilizar a abordagem abaixo que também é thread-safe e lazy-loaded (baseado no código de Bill Pugh). Note que o objeto de criação única pode ser qualquer classe de escolha do programador. Esta não precisa ser necessariamente um SingletonTest e tão pouco estar em uma inner class.
public final class SingletonTest {
private SingletonTest() {
}
final static class SingletonContainer {
final static SingletonTest _Instance = new SingletonTest();
}
public static synchronized SingletonTest getInstance() {
return SingletonContainer._Instance;
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
}
O código acima é específico para um Singleton (apenas lembrando que um Multiton não é um padrão especificado pela Gang-Of-Four [não que seja errado não ser da Gang-Of-Four]). Então estamos tratando apenas de uma instância do objeto. Note o uso da keyword final em diferentes contextos. SingletonTest não mais aceitará herança (e todos os seus métodos se tornam finalizadores) e _Instance se torna um membro global constante da inner class SingletonContainer. Um último detalhe para os que não sabem, a palavra final utilizada em métodos, “tranca” o mesmo para que sua implementação não seja redefinida em outras classes. Aos amantes de C++ (como eu), todos os métodos em Java por default são virtuais.
Existe uma outra variação de Singleton para métodos sincronizados chamado Double-Checked Locking. Esta abordagem tenta sincronizar apenas a criação do objeto, sem a necessidade de sincronização de todo o método, diminuindo a penalidade da sincronização. Você pode conferir mais deste pattern em http://www.google.com.br/search?hl=pt-BR&rlz=1B3GGGL_pt-BRBR226BR226&q=double-checked+Locking&btnG=Pesquisar&meta= (pesquisa Google) mas há vários indícios de que este método é “quebrado”. Futuramente escreverei uma matéria sobre este.
Percebeu então como a implementação de um Singleton é simples. Com algumas linhas de código conseguimos definir uma classe bastante útil para manipulação de apenas uma instância de objeto. Este padrão não termina por aqui. Você pode modificá-lo e reorganizá-lo de acordo com suas necessidades específicas. O importante é entender como o padrão funciona e onde exatamente utilizá-lo. Procure mais informações sobre o mesmo, veja implementações diferentes. Só praticando você conseguirá dominar a regra e utilização deste pattern para que este se aplique aos seus interesses perfeitamente. Estude também os outros design patterns existentes e aprenda a programar em cima destes que são ótimas formas de reutilização de software e de organização do mesmo. Se você souber o que realmente é e como funciona um Singleton, você será capaz de implementá-lo em qualquer linguagem. Uma boa semana a todos e bons estudos.
Um grande abraço.
“O único lugar aonde o sucesso vem antes do trabalho é no dicionário.”
Einstein