3 de agosto de 2009

Design Patterns com Delphi : Singleton

Há situações no projeto de um sistema em que claramente deve existir apenas uma instância de uma determinada classe. Geralmente, são classes criadas para gerenciar o acesso a um recurso controlado do sistema, isto é, aqueles recursos que devem ser acessados com cautela e sob certas regras para evitar o caos no sistema. São exemplos dessa situação o gerenciamento de recursos como Janelas, Impressão de relatórios e o gerenciamento de conexões com o banco de dados.

Para garantir a existência de apenas uma única instância de uma classe foi criado o Design Pattern criacional Singleton. Veja o esquema UML para esse padrão:

Esquema para Singleton
O diagrama mostra que o padrão evita a criação de múltiplas instâncias fazendo com que o constructor seja declarado com a visibilidade private (privada), de forma que apenas a própria classe tem acesso a ele.

Como então é criada uma instância para a classe ? A resposta também aparece no diagrama acima: uma variável privada da classe e um método de classe público. A diferença entre esse método e o constructor é que o método só gera uma nova instância da classe se isso ainda não foi feito antes. Esse efeito é conseguido testando-se a variável privada. Se a variável ainda está vazia (com Nil), significa que não há uma instância criada. Neste caso, cria-se a instância e armazena-se na variável de classe, que é então devolvida pela função. Se uma instância já foi criada antes, a variável de classe não estará mais vazia, de modo que a função simplesmente retorna o valor armazenado na variável.

Mapeando estes conceitos para a linguagem Delphi, temos a seguinte definição de classe e da função GetInstance:
type TWGerImpressao=class
private
constructor Create;
public
class function GetInstance : TWGerImpressao;

procedure ImprimeJob;
end;

implementation

var
_Gerenciador : TWGerImpressao;

class function TWGerImpressao.GetInstance : TWGerImpressao;
begin
if (_Gerenciador = Nil) then
_Gerenciador := TWGerImpressao.Create;
Result := _Gerenciador;
end;

Diferentemente do Delphi para .Net, o Delphi para Win32 não possui o conceito de variável de classe - apenas métodos podem ser de classe. Por isso, criei a variável _Gerenciador como parte da área implementation da unit, de forma que ela só pode ser acessada por métodos internos da unit. Essa solução funciona tanto para Win32 quanto para .Net mas falta iniciar a variável com Nil para o código funcionar. Como o _Gerenciador pertence à unit, o melhor lugar para atribuir-lhe um valor inicial é na área initialization. Da mesma maneira, o código de finalização da unit só é chamado quando ela não é mais necessária e, por isso, devo chamar o destrutor no finalization para liberar a memória e outros recursos:
initialization
_Gerenciador := Nil;

finalization
if (_Gerenciador <> Nil) then
_Gerenciador.Free;
end.

Um último lembrete. Se for utilizar esse código para implementar o Singleton em ambiente multi-thread, não esqueça de envolver a criação da instância numa seção crítica. Isso evitará que duas threads acessando simultaneamente decidam realizar a criação por ter encontrado o _Gerenciador com Nil.

3 comentários :

Rafael Pimenta disse...

Olá Luiz, estou lendo seus artigos sobre Design Patterns. Ainda não li todos, mas os que li gostei bastante! Show!

Mto interessante o Singleton, vejo bastante utilidade nele, desde que bem implementada.

há,o Delphi possui variável de classe sim, acho que a partir da versão 2006:

type
TWGerImpressao = class
private
class var _Gerenciador: TWGerImpressao;
end;

Só uma dica mesmo.

Parabens pelos artigos.
Forte abraço.

Luís Gustavo Fabbro disse...

Rafael

Obrigado pela dica ! Tenho dedicado um tempo pra ver os recursos introduzidos nas versões mais recentes mas ainda não tinha visto as variáveis de classe no Delphi.

Vou ver se posto sobre esses novos recursos : RTTI, interfaces, novas sintaxes, iterators, etc...

[]s

Unknown disse...

Luís, seu blog é fantástico e me ajudou muito em diversas situações. Gostaria de deixar aqui uma contribuição e, ao mesmo tempo, solicitar uma sugestão sobre esta ser ou não uma forma adequada de se aplicar o padrão Singleton. Penso que este seja um dos mais, digamos, polêmicos a serem aplicados, pois possui diversas "versões", sempre batendo na tecla de que não deve ser possível se chamar um construtor do objeto de qualquer parte do código, mas somente acessar sua instância única. A minha implementação é a seguinte:

//// CLASSE ABSTRATA
TSingleton = class(TObject)
constructor Create;
public
class function Singleton: TObject; virtual;
end;

implementation

constructor TSingleton.Create;
begin
Self.Singleton;
end;

class function TSingleton.Singleton;
begin
Result:= Self.NewInstance;
end;


////// CLASSE CONCRETA
TControleSessao = class(TSingleton)
public
class function Singleton: TControleSessao;

implementation

var
FControleSessao: TControleSessao;

class function TControleSessao.Singleton: TControleSessao;
begin
if FControleSessao = nil then
begin
FControleSessao:= TControleSessao(inherited Singleton);
// Outras implementações de inicialização
end;
Result:= FControleSessao;
end;

finalization
if FControleSessao <> nil then
FreeAndNil(FControleSessao);


Valew, e parabéns por essa grande fonte de informação a toda a comunidade Delphi!

Postar um comentário

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.

Observação: somente um membro deste blog pode postar um comentário.