Padrão Singleton em Java

1. Introdução

Um Singleton é um padrão de projeto bastante conhecido pela maioria dos programadores, trata-se de uma classe que é instanciada apenas uma vez. Os Singletons normalmente são componentes únicos do sistema, como por exemplo, um gerenciador de janela num sistema operacional ou o sistema de arquivos. Em ambos os exemplos, tanto o gerenciador de janelas quanto o gerenciador de arquivos são um componente único que gerencia tudo em volta do sistema que se refere tanto às janelas quanto ao sistema de arquivos. Para um melhor esclarecimento, consulte os outros artigos a respeito do assunto que estão disponibilizados no portal.

2. Abordagens para criar um Singleton

Existem algumas abordagens para criar um Singleton. A primeira delas é uma abordagem bastante antiga onde temos a classe com um construtor privado e exportamos um membro estático público para dar acesso à instância exclusiva. O exemplo abaixo demonstra como isso é de fato feito em Java:

Listagem 1: Exemplo de implementação de um Singleton.


public class GerenciadorDeJanelas {
  public static final GerenciadorDeJanelas INSTANCE = new GerenciadorDeJanelas();

  private GerenciadorDeJanelas() {
  }
}

O construtor privado é chamado apenas uma única vez para inicializar o campo final estático INSTANCE. Como não temos um construtor público ou protegido, temos a garantia de que existirá apenas uma instância para a classe GerenciadorDeJanelas.

Para burlar isso e criar uma segunda instância dessa classe, um cliente poderia chamar o construtor privado reflexivamente com a ajuda do método AcessibleObject.setAcessible. Para defender-se deste ataque, o construtor teria que ser modificado para lançar uma exceção caso fosse solicitado a criar uma segunda instância.

Outra abordagem para implementar um Singleton é através de um método de fabricação estático. Por exemplo:

Listagem 2: Exemplo de implementação de um Singleton com um método de fabricação estático


public class GerenciadorDeJanelas {
  private static final GerenciadorDeJanelas INSTANCE = new GerenciadorDeJanelas();

  private GerenciadorDeJanelas() {
  }

  public static GerenciadorDeJanelas getInstance() {
    return INSTANCE;
  }
}

Temos no exemplo acima que todas as chamadas a GerenciadorDeJanelas.getInstance() retornam a mesma referência de objeto. A grande vantagem desta abordagem é que através de um campo público, torna-se claro que esta classe é um Singleton. Outra vantagem dessa abordagem é se quisermos retornar uma instância diferente para cada chamada, ou seja, mudar completamente o comportamento. Nesse tipo de implementação, alterar esse comportamento seria bastante simples.

O grande problema das duas abordagens anteriores aparece se quisermos torna-las serializáveis, apenas adicionar implements Singleton não é o suficiente. Para preservar a propriedade Singleton, teríamos que tornar todos os campos de instância transientes e fornecer um método readResolve. Caso contrário, sempre que desserializarmos uma instância teríamos uma nova instância criada, para evitar isso deveríamos criar o método abaixo:

Listagem 3: Exemplo de método para retornar o objeto


private Object readResolve() {
return INSTANCE;
}

Para mais informações sobre objetos serializáveis em Java, consulte os artigos do portal que falem sobre o assunto.

No entanto, existe uma terceira abordagem considerada uma prática muito melhor que as duas citadas acima, trata-se de um Singleton feito através de uma Enumeração, como será melhor visto na próxima seção.

3. Utilizando Enumerações para criar um Singleton

Enumerações são tipos de campos que consistem em um conjunto fixo de constantes (static final), sendo como uma lista de valores pré-definidos.

O exemplo abaixo demonstra a facilidade da criação de Singletons utilizando Enumerações:

Listagem 3: Exemplo de criação de Singleton usando enumerações


public Enum GerenciadorDeJanelas {
	INSTANCE;
}

Essa abordagem é equivalente à abordagem de campo público, no entanto é considerada muito mais clara e concisa, além disso, seu mecanismo de desserialização é fornecido mais facilmente e possui uma garantia sólida contra instanciação múltipla. O Enum é a melhor maneira de implementar um Singleton em Java.

A desvantagem dessa técnica é que ela só está disponível a partir da versão 1.5 do Java, onde as enumerações foram disponibilizadas.

4. Conclusão

O padrão Singleton é muito utilizado em diversos frameworks e aplicações que necessitem de componentes únicos. Existem algumas abordagens para criar um Singleton, entre elas, uma através de Enumerações que facilita bastante a sua criação e a clareza do código. Além disso, a criação de Singletons através de enumerações acaba facilitando bastante a programação, pois várias situações são desnecessárias, como a preocupação com a serialização.

Bibliografia

Leia também