Singleton Pattern no Delphi com uma nova abordagem

Recentemente tive a felicidade de ver meu artigo Padrão Singleton com Delphi publicado na edição 110 da revista Active Delphi. Neste artigo explico as características do Design Pattern Singleton (GoF) e mostro algumas maneiras de implementar ele no Delphi.

Simplificando a questão, uma classe Singleton só deve possuir uma instancia em todo contexto da aplicação. A maneira mais básica de implementar o pattern Singleton no Delphi é seguindo o seguinte esqueleto:

type
  TMyClass = class
  strict private
    class var FInstance : TMyClass;
  private
    class procedure ReleaseInstance();
  public
    class function GetInstance(): TMyClass;
  end;

O segredo está no uso do atributo de classe FInstance, que armazena uma instancia da classe em questão. Os métodos GetInstance() e ReleaseInstance() por sua vez são responsáveis por trabalhar com apenas uma instancia da classe, ou seja, a instancia armazenada no atributo FInstance. A implementação desses métodos, garante essa segurança:

class function TMyClass.GetInstance: TMyClass;
begin
  if not Assigned(Self.FInstance) then
    self.FInstance := TMyClass.Create;
  Result := Self.FInstance;
end;

class procedure TMyClass.ReleaseInstance;
begin
  if Assigned(Self.FInstance) then
    Self.FInstance.Free;
end;

initialization
finalization
  TMyClass.ReleaseInstance();

Para utilizar uma classe singleton basta substituir o Create() pelo GetInstance() e não executar o método Free().

var
  oMyClass : TMyClass;
begin
  oMyClass := TMyClass.GetInstance();
  oMyClass.ExecutarAlgo();
  {...}

Ai vem a pergunta, como fica o memory leek? Bem, repare que na implementação da classe singleton o método ReleaseInstance() é executado no finalization da unit, ou seja, quando aquela unit sair do contexto da aplicação, a instancia da nossa classe singleton será liberada da memória.

Pesquisando sobre design patterns em Delphi, me deparei com uma abordagem muito interessante relacionada ao pattern Singleton. Da maneira anterior, a inteligência que faz com que uma classe seja singleton deve ser replicada em toda classe que deve funcionar como singleton. Isso não é tão ruim, visto que estamos lidando com uma implementação simples, porém, não seria interessante poder transformar qualquer classe comum em uma classe singleton? Isso é possível, basta utilizar Generics. Criaremos uma classe genérica chamada TSingleton e ela será o nosso coringa para transformar outras classes comuns em classes singleton.

type
  TSingleton<T: class, constructor> = class
  strict private
    class var FInstance : T;
  public
    class function GetInstance(): T;
    class procedure ReleaseInstance();
  end;

Veja que, desconsiderando o uso de generics, em quase nada a interface da classe é diferente do modelo anterior, assim como também, não há grandes diferenças de implementação.

class function TSingleton<T>.GetInstance: T;
begin
  if not Assigned(Self.FInstance) then
    Self.FInstance := T.Create();
  Result := Self.FInstance;
end;

class procedure TSingleton<T>.ReleaseInstance;
begin
  if Assigned(Self.FInstance) then
    Self.FInstance.Free;
end;

O diferencial aqui fica por conta da flexibilidade. Com essa classe genérica podemos transformar qualquer classe comum em uma classe singleton sem alterar em nada a classe original. Vamos ver na prática, recriando nossa classe TMyClass. Faremos essa classe sem os controles de uma classe singleton e logo em seguida, criaremos uma classe que seja uma opção singleton da classe TMyClass.

type
  TMyClass = class
  strict private
    FValue : integer;
  public
    property Value : Integer read FValue write FValue;
  end;

  TMyClassSingleton = TSingleton<TMyClass>;

Pronto, agora nossa classe TMyClass pode funcionar tanto como uma classe normal, como uma classe singleton, graças a classe TMyClassSingleton. Para garantir que não teremos memory leek, é recomendado liberar a instancia no finalization da unit, assim como feito anteriormente:

initialization
finalization
  TMyClassSingleton.ReleaseInstance();

Em termos de utilização da classe, em nada difere os dois modelos, em ambos utilizamos o GetInstance() no lugar do Create() e dispensamos o uso do Free(). Em um teste simples de uma aplicação console, podemos validar se nossa abordagem está realmente correta:

var
  oSingleton1  : TMyClass;
  oSingleton2  : TMyClass;
begin
  oSingleton1  := TMyClassSingleton.GetInstance();
  oSingleton2  := TMyClassSingleton.GetInstance();
  try
    oSingleton1.Value  := 2;
    oSingleton2.Value  := 3;

    Writeln(Format('Singleton1: %d | Singleton2: %d',[oSingleton1.Value,
                                                      oSingleton2.Value]));
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

A saída do código acima será:

Singleton1: 3 | Singleton2: 3

Ou seja, tanto o objeto oSingleton1 quando o objeto oSingleton2 estão apontando para a mesma instancia da classe TMyClass. São duas abordagens de implementação distintas, com um resultado semelhante, qual você prefere?

Sobre Diego Garcia

Analista/Desenvolvedor Delphi desde 2008, bacharel em Ciência da Computação e entusiasta de metodologias ágeis e engenharia de software.
Esse post foi publicado em Programação e marcado , , , , . Guardar link permanente.

4 respostas para Singleton Pattern no Delphi com uma nova abordagem

  1. Fábio Uberti disse:

    Como posso fazer para acessar uma instância da classe singleton de qualquer outra unit do sistema?

  2. Leo disse:

    Bela dica. Seria legal mostrar como fazer uma classe Singleton usando seu exemplo mas com parâmetros no Create. Aí acho que a RTTI nos salva.
    Abraços

  3. Opa, Diego, tudo bem?

    Uma pequena dúvida: eu poderia criar o Singleton na seção “initialization” ao invés de criá-lo no método “GetInstance”?

    • Diego Garcia disse:

      Olá André, acredito que sim (não consigo testar para validar essa opção).
      Bastaria usar a referencia já criada no “initialization” ao invés de sempre chamar o método “GetInstance”.

Deixe um comentário