Singleton é um padrão criacional que garante uma única instância para um objeto, além de fornecer um ponto de acesso global para essa instância. Em outras palavras, o objeto será criado apenas uma vez em todo o processo.

Imagine que você está construindo um sistema para uma escola. Este sistema deverá viabilizar dentre outras funcionalidades, a matricula de alunos. Durante o processo de cadastro é necessário indicar o diretor vigente, que é único. Dentre várias possibilidades, o padrão Singleton tem o objetivo de solucionar este tipo de problema.

Vamos implementar um código na prática sem um padrão bem definido e com o Singleton. Acompanhe:

Sem Singleton

Classe Diretor com construtor publico (todos podem criar uma nova instância de diretor)

@Getter
public class Diretor {

    private String nome;
    private String sobrenome;

    public Diretor(String nome, String sobrenome) {
        this.nome = nome;
        this.sobrenome = sobrenome;
    }    
}

Sempre que diretor for instânciado, um novo objeto será criado (new Diretor)

public class Main {
    public static void main(String[] args) {

        Diretor diretor = new Diretor("josé", "Silva");
        System.out.println("Nome:" + diretor.getNome());
        System.out.println(diretor);

        Diretor outroDiretor = new Diretor("josé", "Silva");
        System.out.println("Nome:" + outroDiretor.getNome());
        System.out.println(outroDiretor);
    }
}

Image description

Notem que são dois objetos diferentes. Imagine que um aluno foi matriculado e o diretor foi "José", um professor foi contratado e o diretor foi "joão". Cada ponto do sistema, um novo objeto sendo criado e possibilitando trocar o valor dos atributos. Isso pode causar decisões conflitantes, bugs e resultados não esperados. Por isso há a necessidade de criar um objeto único...

Com Singleton

Classe diretor criada, mas dessa vez com um construtor privado. Isso significa que apenas a classe diretor terá acesso ao próprio construtor. Ou seja, ninguém mais poderá criar um Diretor a não ser a própria classe.

@Getter
public class Diretor {
    private static Diretor instance;

    private String nome;
    private String sobrenome;

    private Diretor(String nome, String sobrenome) {
       this.nome = nome;
         this.sobrenome = sobrenome;
    }

    public static Diretor getInstance(String nome, String sobrenome) {
        if (instance == null) {
            instance = new Diretor(nome, sobrenome);
        }
        return instance;
    }

}

Agora a classe cliente deverá chamar o método getInstance() ao invés de new Diretor(..). O método por sua vez, devolverá sempre a mesma instância caso ela já exista.

public class Main {
    public static void main(String[] args) {

        Diretor diretor = Diretor.getInstance("joão", "Silva");
        System.out.println("Nome:" + diretor.getNome());
        System.out.println(diretor);

        Diretor outroDiretor = Diretor.getInstance("fulado", "de tal");
        System.out.println("Nome:" + outroDiretor.getNome());
        System.out.println(outroDiretor);
    }
}

Image description

Notem que agora, o objeto retornado sempre será o mesmo, pois foi instânciado apenas uma vez.

Como na maioria esmagadoras dos casos (quase me atrevo a dizer todos), esta não é a única forma de resolver este problema. Poderíamos usar, por exemplo, a injeção de dependências, deixando o código um pouco mais fácil de manter e testar. Entretanto, o intuito é avaliar este padrão em específico, como implementa-lo, quais suas vantagens e desvantagens.

Existem diversos outros exemplos de implementação do padrão Singleton. Dentre os mais famosos, temos:

  • gerenciamento de configurações de sistemas. Exemplo: o Singleton ser a classe responsável por trocar o tema da IDE. Sempre teremos um único tema por vês e por isso o Singleton faz sentido.

  • gerenciamento de instâncias de banco de dados. Exemplo: ter um Singleton responsável por abrir uma conexão e mantê-la. Disponibilizando-a para quem precisar sem precisar abrir novas conexões, economizando recursos e otimizando performance.

Vantagens

Garantia de unicidade: garante que haja apenas um diretor na escola.
Acesso centralizado: permite que qualquer parte do sistema, como professores, alunos, secretários(...) acesse facilmente as informações e os métodos do diretor.
Controle de recursos: se o diretor precisar gerenciar recursos limitados, como vagas em turmas ou orçamento, o Singleton garante que ele tenha uma visão centralizada e evite conflitos.
Consistência: garante que todos os componentes do sistema estejam trabalhando com as mesmas informações e diretrizes do diretor.

Desvantagens

Responsabilidade única: viola o princípio da responsabilidade única pois a classe Singleton assume a responsabilidade de criar e gerenciar sua própria instância.
Aumenta complexidade de testar: dificulta a criação de testes unitários para as classes que dependem do diretor, pois é difícil substituir o Singleton por um "mock" ou "stub" durante os testes.
Acoplamento global: cria um acoplamento global entre a classe Diretor e as classes que a utilizam, tornando o código menos flexível e mais difícil de modificar.
Dificuldade de escalabilidade: pode dificultar a evolução do sistema, pois a dependência global do diretor pode tornar difícil a introdução de novas funcionalidades ou a modificação das existentes.

Entenda também