Fundamentos e Motivação
O padrão de projeto Singleton assegura que uma classe possua apenas uma instância ativa durante a execução da aplicação, disponibilizando um ponto de acesso global a esse objeto. Essa estratégia é indicada quando uma classe centraliza funcoinalidades específicas ou gerencia recursos compartilhados, como caches, pools de conexões ou gerenciadores de configuração. Ao impedir a criação de múltiplas instâncias, o padrão otimiza o consumo de memória e garante consistência de estado em todo o sistema.
Aspectos Críticos de Implementação
Ao estruturar um Singleton, três pilares técnicos devem ser avaliados:
- Concorrência e thread safety: Evitar que múltiplas threads inicializem objetos duplicados simultaneamente.
- Inicialização tardia (Lazy Loading): Postergar a alocação do objeto até que ele seja realmente requisitado pela aplicação.
- Custo de acesso: Minimizar o uso de mecanismos de bloqueio após a primeira criação, preservando a latência nas chamadas subsequentes.
Em Python, a linguagem oferece recursos nativos que permitem intecreptar o ciclo de vida das classes. Duas das abordagens mais robustas utilizam metaclasses e decoradores para controlar o processo de instanciação.
Controle via Metaclass
Metaclasses definem como outras classes são construídas. Ao personalizar o método __call__, é possível interceptar a tentativa de criação de objetos e redirecionar o fluxo para uma instância já existente, garantindo unicidade sem modificar a lógica interna da classe alvo.
class SingletonMeta(type):
_instance_cache = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instance_cache:
# Aloca o objeto apenas na primeira invocação
fresh_instance = super().__call__(*args, **kwargs)
cls._instance_cache[cls] = fresh_instance
return cls._instance_cache[cls]
class GlobalConfig(metaclass=SingletonMeta):
def __init__(self):
self.parameters = {"env": "production", "timeout": 30}
# Validação de identidade
cfg_first = GlobalConfig()
cfg_second = GlobalConfig()
print(f"Endereço cfg_first: {id(cfg_first)}")
print(f"Endereço cfg_second: {id(cfg_second)}")
print(f"Instância única confirmada: {cfg_first is cfg_second}")
Controle via Decorator
Decoradores permitem encapsular a classe em uma função wrapper, mantendo a referência do objeto em um escopo fechado (closure). Essa técnica desacopla a lógica de singleton da definição da classe, facilitando a reutilização em diferentes componentes do projeto.
def enforce_singleton(target_cls):
_stored_ref = None
def wrapper(*args, **kwargs):
nonlocal _stored_ref
if _stored_ref is None:
_stored_ref = target_cls(*args, **kwargs)
return _stored_ref
return wrapper
@enforce_singleton
class AppLogger:
def __init__(self):
self.history = []
def trace(self, msg):
self.history.append(msg)
# Verificação de comportamento
log_alpha = AppLogger()
log_beta = AppLogger()
print(f"Endereço log_alpha: {id(log_alpha)}")
print(f"Endereço log_beta: {id(log_beta)}")
print(f"Mesma referência na memória: {log_alpha is log_beta}")