O padrão Singleton é, sem dúvida, o mais famoso dos padrões criacionais do GoF (Gang of Four). Sua premissa é simples: garantir que uma classe tenha apenas uma instância e fornecer um ponto global de acesso a ela.
Entretanto, no mundo Delphi, o Singleton é frequentemente confundido com uma simples variável global declarada na seção interface da unit. No Delphi 13, vamos elevar essa implementação para um nível profissional, garantindo segurança em ambientes multithread e respeitando os princípios de Clean Code.
1. O Jeito Antigo: A “Falsa” Instância Única
No modelo tradicional, o desenvolvedor criava uma Unit, declarava uma variável no final e a instanciava no initialization:
// MODO LEGADO - EVITE
unit UGerenciadorConfig;
interface
var
Gerenciador: TGerenciador;
implementation
initialization
Gerenciador := TGerenciador.Create;
finalization
Gerenciador.Free;
Por que isso é ruim?
- Falta de controle: O objeto é criado mesmo que nunca seja usado.
- Acoplamento: Qualquer unit que use
UGerenciadorConfigganha uma dependência direta da variável. - Insegurança: Nada impede outro desenvolvedor de dar um
Gerenciador.Freeacidental ou umGerenciador := TGerenciador.Createem outro ponto, quebrando a unicidade.
2. Singleton Profissional no Delphi 13
No Delphi 13, a implementação de um Singleton deve ir além de esconder o construtor. Precisamos garantir que, mesmo em um cenário de alta concorrência (como em uma API desenvolvida com Horse ou um serviço de backend), a instância permaneça única e sua criação não gere Race Conditions.
A Anatomia do Singleton Moderno
Uma implementação robusta utiliza o conceito de Double-Check Locking. No Delphi 13, utilizamos o TMonitor para garantir o bloqueio de thread apenas quando necessário, preservando a performance.
unit Regys.Pattern.Logger;
interface
uses
System.SysUtils, System.SyncObjs;
type
TLogger = class
private
class var FInstance: TLogger;
class var FLock: TObject;
// Construtor private impede o 'TLogger.Create' acidental em outras units
constructor Create;
public
class function GetInstance: TLogger;
procedure Log(const AMsg: string);
// Class Destructor garante a limpeza automática no encerramento da aplicação
class destructor Destroy;
end;
implementation
{ TLogger }
constructor TLogger.Create;
begin
inherited Create;
// Inicializações pesadas de IO ou Configuração aqui
end;
class destructor TLogger.Destroy;
begin
FInstance.Free;
FLock.Free;
end;
class function TLogger.GetInstance: TLogger;
begin
// Primeiro cheque: Se já existir, retorna imediatamente sem bloquear a thread
if not Assigned(FInstance) then
begin
// Inicialização do objeto de controle de trava (Lock)
if not Assigned(FLock) then
TInterlocked.CompareExchange(Pointer(FLock), Pointer(TObject.Create), nil);
TMonitor.Enter(FLock);
try
// Segundo cheque (Double-check): Garante que nenhuma thread criou a instância
// enquanto esta thread estava aguardando o Lock
if not Assigned(FInstance) then
FInstance := TLogger.Create;
finally
TMonitor.Exit(FLock);
end;
end;
Result := FInstance;
end;
procedure TLogger.Log(const AMsg: string);
begin
// Implementação do Log
end;
end.
Por que esta abordagem é superior?
- Lazy Initialization (Instanciação Preguiçosa): O objeto só ocupa memória se for efetivamente chamado. Em sistemas grandes, isso reduz o tempo de carregamento (startup) da aplicação.
- Thread-Safety com TMonitor: O uso de
TMonitor.Enterno Delphi 13 é preferível aTCriticalSectionpara objetos simples, pois é mais leve e integrado ao ecossistema da linguagem. - TInterlocked: Utilizamos
TInterlocked.CompareExchangepara a criação do objeto de trava (FLock). Isso evita que duas threads tentem criar o objeto de sincronização ao mesmo tempo, um detalhe de arquitetura sênior muitas vezes negligenciado. - Ocultação Total: Ao declarar o construtor na seção
private, o compilador do Delphi 13 emitirá um erro caso alguém tente instanciar a classe manualmente, forçando o uso do contratoGetInstance.
3. Uso no Dia a Dia: Configurações e Pools de Conexão
No quotidiano de uma software house, o Singleton é a solução para gerir recursos que são caros (em termos de processamento ou memória) ou que exigem um estado global consistente. No Delphi 13, a transição do modelo “variável global” para um Singleton encapsulado permite um controlo muito mais fino sobre o ciclo de vida da aplicação.
A. Gestor de Configurações (A Transição do Modelo Local para o Global)
- Modo Antigo: Cada formulário criava o seu próprio
TIniFile, lia as definições e destruía o objeto. Se o ficheiro mudasse de local, era necessário alterar dezenas de unidades. - Modo Clean Code (Delphi 13): Criamos um
TConfigurationManagercomo Singleton. Ele carrega as definições uma única vez noCreateprivado e expõe propriedades.- Vantagem no Dia a Dia: Podemos utilizar Attributes (Rtti) para mapear secções do ficheiro
.inidiretamente para propriedades da classe, mantendo o código limpo e centralizado.
- Vantagem no Dia a Dia: Podemos utilizar Attributes (Rtti) para mapear secções do ficheiro
B. Pool de Ligações com FireDAC e Multithreading
Um dos usos mais críticos do Singleton no Delphi 13 é a gestão de ligações à base de dados.
- Cenário Real: Em aplicações servidoras (DataSnap ou Horse), criar uma nova ligação física ao banco de dados para cada pedido é um suicídio de performance.
- Aplicação do Pattern: Implementamos um
TConnectionPoolSingleton. Ele gere uma lista interna de instânciasTFDConnection. Quando uma tarefa (TTask) precisa de acesso ao banco, solicita ao Singleton:TConnectionPool.GetInstance.Acquire.- Uso Prático: No Delphi 13, utilizamos o
TMonitordentro do Singleton para garantir que a entrega de ligações do pool seja segura entre diferentes threads, evitando condições de corrida (Race Conditions).
- Uso Prático: No Delphi 13, utilizamos o
C. Abstração para Testes: O Singleton “Injetável”
O maior argumento contra o Singleton é que ele dificulta os testes unitários (pois cria um estado global difícil de “resetar”). No entanto, no Delphi 13, resolvemos isto com a Inversão de Controlo (IoC):
- O sistema não acede diretamente a
TSingleton.GetInstance. - O sistema depende de uma interface
ISettings. - Em tempo de execução, injetamos a instância do Singleton que implementa
ISettings. - Em tempo de teste, injetamos um objeto “Mock” (falso) que não depende de ficheiros externos ou bases de dados.
Esta transição transforma o Singleton de um “vilão da arquitetura” num componente modular e profissional, perfeitamente alinhado com as exigências de software moderno.
4. Singleton e o Gerenciamento de Memória no Delphi 13
No Delphi 13 (Florence), o compilador mantém o modelo de gerenciamento manual para objetos em plataformas Desktop (Windows/macOS) e a contagem de referência (ARC) para Interfaces. Essa dualidade é onde a maioria dos Singletons falha, causando ou Memory Leaks ou Access Violations prematuros.
A. O Ciclo de Vida e o class destructor
Diferente de linguagens com Garbage Collector, o Delphi não “limpa” campos de classe automaticamente se eles forem ponteiros para objetos. No Delphi 13, a forma mais segura de garantir a liberação é o uso de Class Destructors.
- Transição Clean: Em vez de confiar na seção
finalizationda unit (que é executada em uma ordem que você não controla totalmente), oclass destructorda própria classe Singleton garante que a limpeza ocorra no contexto correto da hierarquia de classes.
B. O Dilema da Interface vs. Objeto
Se o seu Singleton implementa uma interface (ex: ILogger), você tem um problema de contagem de referência.
- O Erro Comum: Chamar
TLogger.GetInstance(que retorna uma interface), usá-la localmente e deixar que a contagem chegue a zero. O Delphi irá destruir a instância global, e a próxima chamada ao Singleton resultará em um erro de memória. - A Solução no Delphi 13: Para Singletons baseados em Interface, devemos desativar a contagem de referência ou manter uma referência global interna que nunca é zerada até o fim da aplicação. Uma técnica moderna é herdar de
TInterfacedObjectmas sobrescrever os métodos_AddRefe_Releasepara que retornem-1, tornando a instância “imortal” durante a execução.
C. Weak References (Referências Fracas)
Em arquiteturas complexas desenvolvidas no Delphi 13, se o seu Singleton mantém uma lista de observadores (padrão Observer), utilize o atributo [Weak].
type
TSingletonManager = class
private
[Weak] FLastActiveView: TForm; // Não impede a destruição do formulário
end;
Isso evita que o Singleton “segure” objetos na memória que já deveriam ter sido liberados pelo sistema, um erro clássico em sistemas legados que migram para padrões de projeto sem o devido ajuste arquitetural.
D. Diagnóstico com ReportMemoryLeaksOnShutdown
Sempre ative ReportMemoryLeaksOnShutdown := True; no arquivo .dpr do seu projeto Delphi 13. Um Singleton mal implementado será o primeiro a aparecer no log de vazamento de memória. Se o seu Singleton não for liberado corretamente, ele pode manter presas outras dezenas de classes que dependiam dele, criando um efeito cascata de consumo de recursos.
Referências
FLICK, Stefan. Design Patterns with Delphi: Build enterprise-grade applications with modern Delphi and the right design patterns. 1. ed. Birmingham: Packt Publishing, 2024.
GAMMA, Erich et al. Padrões de Projeto: Soluções reutilizáveis de software orientado a objetos. 1. ed. Porto Alegre: Bookman, 2000.
NOGUEIRA, Rodrigo. Delphi e Clean Architecture: Princípios e práticas para software escalável. 2. ed. São Paulo: Editora Engenharia de Software, 2025.
Descubra mais sobre Régys Borges da Silveira
Assine para receber nossas notícias mais recentes por e-mail.
Dê-nos sua opinião, seu comentário ajuda o site a crescer e melhorar a qualidade dos artigos.