Singleton
O Singleton é um padrão criacional que garante que uma classe possua uma única instância durante o ciclo de vida da aplicação e fornece um ponto global de acesso a ela.
A ideia é:
Aplicação
│
├── getInstance()
│
├── getInstance()
│
└── getInstance()
│
↓
Mesma instância
Independentemente de quantas vezes solicitarmos a instância, sempre receberemos o mesmo objeto.
Implementação
class Database {
private static instance:
Database;
private constructor() {}
static getInstance(): Database {
if (!Database.instance) {
Database.instance =
new Database();
}
return Database.instance;
}
connect() {
console.log(
"Conectado ao banco"
);
}
}
O construtor é:
private constructor()
Isso impede que alguém faça:
const database =
new Database();
A única maneira de obter a instância é:
const database =
Database.getInstance();
Testando
Podemos solicitar duas instâncias:
const db1 =
Database.getInstance();
const db2 =
Database.getInstance();
Agora:
console.log(
db1 === db2
);
Resultado:
true
As duas variáveis apontam para a mesma instância.
Exemplo prático: configuração
Um possível uso seria centralizar configurações:
class Config {
private static instance: Config;
private constructor(
public readonly apiUrl =
"https://api.example.com"
) {}
static getInstance() {
if (!Config.instance) {
Config.instance =
new Config();
}
return Config.instance;
}
}
Uso:
const config =
Config.getInstance();
console.log(
config.apiUrl
);
Qualquer parte da aplicação que chamar:
Config.getInstance();
receberá a mesma instância.
O problema do Singleton
Apesar de ser simples, o Singleton é um padrão controverso.
Como existe uma instância global, ele pode criar um estado global difícil de controlar.
Por exemplo:
Database.getInstance()
pode ser chamado de qualquer lugar.
Isso aumenta o acoplamento.
Também pode dificultar:
- Testes unitários.
- Mocking.
- Isolamento de dependências.
- Concorrência.
- Gerenciamento de estado.
Por isso, em aplicações modernas, muitas vezes preferimos utilizar Dependency Injection.
Por exemplo:
class UserService {
constructor(
private database: Database
) {}
}
Em vez de:
class UserService {
private database =
Database.getInstance();
}
Com Dependency Injection, a dependência é fornecida externamente.
Isso facilita testes:
const mockDatabase =
new MockDatabase();
const service =
new UserService(
mockDatabase
);
Quando usar?
O Singleton pode fazer sentido quando realmente precisamos garantir uma única instância.
Exemplos possíveis:
Configuração global
Gerenciadores específicos
Cache global
Logger
Recursos compartilhados
Porém, deve ser utilizado com cuidado.
Resumo
O Singleton garante:
Instância 1 ──┐
│
Instância 2 ──┼──→ Mesmo objeto
│
Instância 3 ──┘
A ideia principal é:
Garantir que exista apenas uma instância de uma determinada classe e fornecer um ponto centralizado para acessá-la.
Entretanto, o Singleton não deve ser utilizado simplesmente porque "precisamos acessar algo globalmente". Muitas vezes, Dependency Injection é uma alternativa mais flexível e testável.