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:

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

  1. Criar classe que tem como atributo static uma classe do mesmo tipo, bem como outros atributos que forem úteis.
  2. Criar construtor privado que retorna valores padrões previamente estabelecidos.
  3. Criar método público static comumente chamado de getInstance() 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.
  4. Criar getters e setters
  5. 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());
    }
}