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?
Como posso fazer para acessar uma instância da classe singleton de qualquer outra unit do sistema?
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
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”?
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”.