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);
}
}
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);
}
}
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.