O padrão de design Singleton tem como objetivo principal garantir que uma classe tenha apenas uma única instância e fornecer um ponto de acesso global a essa instância. Esse padrão é usado para resolver problemas específicos relacionados à gestão de recursos e representação única em toda a aplicação. Os problemas que o padrão Singleton busca resolver incluem:
-
Controle de Recursos Compartilhados: Em muitas situações, é necessário garantir que apenas uma instância de uma classe esteja gerenciando um recurso compartilhado. Isso pode incluir conexões com bancos de dados, arquivos de configuração, spoolers de impressão, entre outros.
-
Consistência de Estado: Quando é necessário que diferentes partes do programa compartilhem um mesmo estado ou configuração, o Singleton assegura que todas as partes estão acessando e modificando uma única instância, mantendo a consistência.
-
Redução de Overhead de Criação de Objetos: A criação de múltiplas instâncias de uma classe pode ser custosa em termos de recursos e desempenho, especialmente se a instância for de uso comum e requerer inicialização pesada ou configuração. O Singleton evita esse overhead ao reutilizar a mesma instância.
-
Substituição de Variáveis Globais: O Singleton pode ser usado para substituir variáveis globais, oferecendo uma abordagem orientada a objetos para acessar um recurso ou serviço global, com benefícios adicionais como atrasar a inicialização (lazy initialization) e garantir a segurança em ambientes multithread.
-
Controle de Acesso: O padrão permite um controle mais refinado sobre como e quando o acesso à instância única é realizado, possibilitando a implementação de lógicas adicionais, como lazy initialization ou tratamento de exceções na criação da instância.
Em resumo, o padrão Singleton é útil em situações onde uma única instância de uma classe deve ser mantida durante o ciclo de vida da aplicação, para gerenciar recursos compartilhados, manter consistência de estado, e oferecer um ponto de acesso global e controlado.
Exemplo de implementação LAZY
- Criar classe que tem como atributo
staticuma classe do mesmo tipo, bem como outros atributos que forem úteis. - Criar construtor privado que retorna valores padrões previamente estabelecidos.
- Criar método público
staticcomumente chamado degetInstance()e retorna o atributo static. Esse método faz a verificação se o objeto é null. Se for, cria um novo. Isso garante que só existe um. - Criar getters e setters
- Para usar essa classe, basta chamar o método
.getInstance()
Definindo a classe Singleton
public class Configuracao {
// Instância única
private static Configuracao instancia;
// Outros atributos da classe
private String propriedade;
// Construtor privado para prevenir instanciação externa
private Configuracao() {
// Inicialização de atributos, por exemplo, ler de um arquivo
propriedade = "Valor padrão";
}
// Método público estático para acessar a instância
public static Configuracao getInstancia() {
if (instancia == null) {
instancia = new Configuracao();
}
return instancia;
}
// Métodos para acessar e modificar as configurações
public String getPropriedade() {
return propriedade;
}
public void setPropriedade(String propriedade) {
this.propriedade = propriedade;
}
}
Utilizando a classe Singleton
public class Main {
public static void main(String[] args) {
// Acessando a instância do Singleton e usando seus métodos
Configuracao configuracao = Configuracao.getInstancia();
System.out.println(configuracao.getPropriedade());
// Alterando a propriedade
configuracao.setPropriedade("Novo Valor");
System.out.println(configuracao.getPropriedade());
}
}Lazy x Eager
O exemplo acima é Lazy, isso é, a instância só é criada até ser chamada. Porém, é possível implementar como Eager, ou seja, a instância é criada no momento que a classe for criada pelo classloader da JVM, por uma inicialização estática. Abaixo está a diferença de implementação.
public class Configuracao {
// Instância única criada durante o carregamento da classe
private static final Configuracao instancia = new Configuracao();
// Outros atributos da classe
private String propriedade;
// Construtor privado
private Configuracao() {
// Inicialização de atributos
propriedade = "Valor padrão";
}
// Método público estático para acessar a instância
public static Configuracao getInstancia() {
return instancia;
}
// Métodos para acessar e modificar as configurações
public String getPropriedade() {
return propriedade;
}
public void setPropriedade(String propriedade) {
this.propriedade = propriedade;
}
}
public class Main {
public static void main(String[] args) {
// Acessando a instância do Singleton
Configuracao configuracao = Configuracao.getInstancia();
System.out.println(configuracao.getPropriedade());
// Alterando a propriedade
configuracao.setPropriedade("Novo Valor");
System.out.println(configuracao.getPropriedade());
}
}