Este é um post disponível para assinantes MVPPadrão Singleton - Revista ClubeDelphi 133
O artigo tratará do padrão de projeto Singleton, mostrando como implementá-lo e que erros comuns devem ser evitados. Também serão abordadas certas peculiaridades provindas dos novos recursos da Delphi Language que podem ajudar na construção de S
[Artigo já está disponível no Leitor Digital DevMedia®. Clique aqui para acessá-lo]
> Clique aqui para ler todos os artigos da ClubeDelphi 133
Singleton é um padrão de projeto bastante conhecido da GOF (Gang of Four) onde uma classe pode ter apenas uma instância. Ele tenta garantir que a classe seja instanciada uma única vez e fornece um ponto de acesso global a ela.
Alguns componentes geralmente modelados como singletons são: camadas de acesso a dados, serviços de comunicação, como envio de e-mails, o objeto de configuração do sistema como um todo e toda e qualquer informação que tenha uma fonte única e que deva ser compartilhada por todas as outras classes.
Especialmente se uma classe de serviço ou de sistema é muito grande e custosa para se instanciar o uso de singleton faz com que durante toda a execução do sistema a classe seja instanciada uma única vez.
Classes que usam recursos escassos e / ou que não podem ser compartilhados, por exemplo, no caso de leitura e escrita em um arquivo de texto, apenas uma instância da classe teria acesso para leitura e escrita por vez. Outras tentativas de abrir um mesmo arquivo para leitura e escrita resultariam em um deadlock.
O uso de singletons deve ser justificado, visto que recentemente muitos têm considerado o singleton um anti-pattern. Afinal muitos dos problemas que alguns programadores costumam resolver usando singletons são possivelmente solucionáveis com injeção de dependência.
Singletons não devem ser usados como variáveis globais, mas sim como objetos que mantêm um estado comum e público a todas as classes do sistema. É o tipo de padrão de projeto que, na dúvida, é melhor não usar. Ele não deve ser usado como uma “variável global orientada a objeto”, nunca.
Para explicar o porquê disso, deve-se raciocinar: se os singletons não devem ser criados, eles também não devem ser destruídos, pois uma vez destruídos quem os recriará? E pior: as referências a ele (variáveis que o contém/apontam para ele) passam a ser inválidas no momento em que ele é destruído. Inválidas, porém diferentes de nil. Criar um singleton que já tem uma instância também é problemático. Depois que a segunda instância é criada e atribuída ao objeto global a referência ao objeto anterior é perdida. Sem referência, como o objeto será destruído? Causa-se assim um memory leak.
Um singleton também não deveria ser atribuível a outras variáveis ou passado como parâmetro para um método, pois não dá para garantir muito o que se fará de errado com ele.
Na seção de links desse artigo encontra-se uma série de URLs de blogs e fóruns discutindo sobre o uso de singletons e porque eles são considerados um anti-pattern.
Note que o que está sendo questionado é o uso indiscriminado do singleton como variável global. Para programadores que estão saindo de ambientes procedurais ou orientados a eventos e tendo suas primeiras incursões na programação OO pura os singletons podem parecer uma atraente alternativa às variáveis públicas globais.
Os leitores deste
artigo poderão se perguntar: “mas para que fazer um artigo sobre esse assunto
se não há consenso com relação aos seus benefícios?”. A resposta é simples,
independente do uso correto do singleton saber como criá-lo é um conhecimento
interessante para habitar a “caixa de ferramentas” de qualquer programador, mas
o mais interessante é que o modo de se criar tal padrão pode mostrar detalhes
interessantes do próprio Delphi.
Maneiras de implementar
A maneira mais comum de se implementar um singleton é por ter uma variável pública na seção implementation da unit onde a classe reside a fim de guardar uma instância da classe a ser criada, e colocar na seção interface uma função estática pública global que retorne esta instância caso criada. Se a variável for nil então a função primeiro a cria e depois a retorna.
Uma melhoria nesse tipo de implementação seria transformar a variável pública global em uma class var (campo estático de classe) que seria limitada unicamente ao escopo da classe, e uma class function (método de classe) para substituir a função estática pública global.
As desvantagens claras dessas duas abordagens é que elas não estão protegidas caso um programador desavisado que não tenha lido o “manual” resolva usar o método create, ou pior, o Destroy.
"ATENÇÃO! A exibição deste artigo foi interrompida.
Este é um post disponível para assinantes MVP
3 COMENTÁRIOS
Não sei se está conforme os padrões, mas para o Delphi2007 fiz assim.
type
TSingleton = class
public
class function GetInstance: Tsingleton;
class function NewInstance: TObject; override;
procedure FreeInstance; override;
function DoSomethingImportant: string;
end;
implementation
var
_Instance: Tsingleton;
FCreationTimeStamp:string;
class function Tsingleton.GetInstance: Tsingleton;
begin
inherited Create;
Result := _Instance;
end;
class function Tsingleton.NewInstance: TObject;
begin
if _Instance = nil then
begin
FCreationTimeStamp:= FormatDateTime('ddmmyyyy_hhnnsszzz', now);
_Instance := (inherited NewInstance as Tsingleton);
end;
Result := _Instance;
end;
function Tsingleton.DoSomethingImportant: string;
begin
Result := 'Instância de ' + Self.ClassName + ' Criada em ' +
FCreationTimeStamp + ' no endereço ' + IntToStr(integer(self));
end;
procedure Tsingleton.FreeInstance;
begin
//destruirá apenas quando for permitido
if _Instance <> nil then
begin
inherited FreeInstance;
_Instance := nil;
end;
end;
(Vagner)
Infelizmente não tenho o Delphi 2007 para testar, mas se você acompanhar os exemplos do meu blog verá que é possível criar boas implementações de singleton mesmo no Delphi 7 ou lazarus.
O importante do singleton é ter mecanismos que assegurem sua única instância, que não seja possível criar uma segunda por engano e que ele não cause um leak.
O artigo na Clube Delphi 133 pode ser interpretado como a "parte 5" dessa série sobre singletons, já que usei uma abordagem um pouquinho diferente.
http://blog.vitorrubio.com.br/2010/11/existem-1001-maneiras-de-preparar.html
unit singleton;
interface
uses sysutils;
type
TSingleton = class
private
FCreationTimeStamp:string;
public
class function GetInstance: Tsingleton;
class function NewInstance: TObject; override;
procedure FreeInstance; override;
function DoSomethingImportant: string;
end;
procedure FreeFirstNilAfter(var Obj); //MeuFreeAndNilBom
procedure FreeFirstNilAfterOneReference(var Obj); //OutroFreeAndNilBom
implementation
var
//Se o Delphi 2007 não suportasse class vars tudo bem colocar esse campo aqui, mas creio que ele suporta:
//consulte os sites:
//http://www.marcocantu.com/md2005/UpdateDelphi2006_ch04.html
//http://hallvards.blogspot.com/2007/05/hack17-virtual-class-variables-part-i.html
//http://edn.embarcadero.com/article/34324
//https://forums.embarcadero.com/thread.jspa?threadID=24246
_Instance: Tsingleton;
//eu aconselho a colocar FCreationTimeStamp como um campo de instância privado pois ele é parte intrinseca da lógica de negócio
//da instância do singleton. Ele não faz sentido fora da classe e não tem porque ser uma variável estática global.
//lembre-se que ele é usado apenas em DoSomethingImportant, que é um método de instância.
//como NewInstance é um método de classe inicialize FCreationTimeStamp apenas no Create.
//se quiser continuar usando no escopo global de implementation use outro prefixo no lugar de F, pois F é para fields de classes.
//usa-lo no NewInstance também é possível como abaixo, mas lembre-se que FCreationTimeStamp é apenas um exemplo que significa uma
//propriedade, valor ou recurso do seu negócio, e DoSomethingImportant é algo importante que só o seu singleton pode fazer.
//FCreationTimeStamp:string;
class function Tsingleton.GetInstance: Tsingleton;
begin
// inherited Create;
// Result := _Instance;
// essa parte pode ficar assim:
Result := inherited Create;
// Lembre que chamar Create é como chamar um atalho "especial" para NewInstance, ou seja, já está jogando _Instance no Result
end;
class function Tsingleton.NewInstance: TObject;
begin
if _Instance = nil then
begin
_Instance := (inherited NewInstance as Tsingleton);
//se quiser continuar sem um construtor atribua o valor a FCreationTimeStamp depois de instanciar, e precedido de _Instance
_Instance.FCreationTimeStamp:= FormatDateTime('ddmmyyyy_hhnnsszzz', now);
end;
Result := _Instance;
end;
function Tsingleton.DoSomethingImportant: string;
begin
Result := 'Instância de ' + Self.ClassName + ' Criada em ' +
FCreationTimeStamp + ' no endereço ' + IntToStr(integer(self));
end;
procedure Tsingleton.FreeInstance;
begin
//destruirá apenas quando for permitido
if _Instance <> nil then
begin
inherited FreeInstance;
_Instance := nil;
end;
end;
procedure FreeFirstNilAfter(var Obj); //MeuFreeAndNilBom
var
Temp: TObject;
begin
Temp := TObject(Obj); //primeiro joga a instancia num objeto temporario
Temp.Free; //invoca free na segunda referência, temporária, que será liberada
Pointer(Obj) := nil; //nulifica a variável por último
end;
procedure FreeFirstNilAfterOneReference(var Obj); //OutroFreeAndNilBom
begin
TObject(Obj).Free; //invoca free
Pointer(Obj) := nil; //nulifica a variável
end;
initialization
finalization
//não se esqueça da seção initilization e finalization para que o
//singleton possa ser de fato destruido
if _Instance <> nil then
begin
FreeFirstNilAfter(_Instance); //funciona
//FreeFirstNilAfterOneReference(_Instance); //funciona
//FreeAndNil(_Instance); //não funciona
//_Instance.Free;
//_Instance := nil;
end;
//FreeAndNil(_Instance); //não funciona
//nunca use FreeAndNil aqui pois freeandnil atribui nil à referência antes
//de efetivamente destruir a instância, o que causaria leak e faria FreeInstance falhar
//use free e depois atribua nil
{
isso é curioso, veja o source de freeandnil:
procedure FreeAndNil(var Obj);
var
Temp: TObject;
begin
Temp := TObject(Obj); //primeiro joga a instancia num objeto temporario
Pointer(Obj) := nil; //nulifica a variável e perde a referencia antes de invocar free
Temp.Free; //invoca free na segunda referência, temporária, que será liberada
end;
como FreeAndNil poderia ser:
procedure FreeFirstNilAfter(var Obj); //MeuFreeAndNilBom
var
Temp: TObject;
begin
Temp := TObject(Obj); //primeiro joga a instancia num objeto temporario
Temp.Free; //invoca free na segunda referência, temporária, que será liberada
Pointer(Obj) := nil; //nulifica a variável por último
end;
ou
procedure FreeFirstNilAfterOneReference(var Obj); //OutroFreeAndNilBom
begin
TObject(Obj).Free; //invoca free
Pointer(Obj) := nil; //nulifica a variável
end;
}
end.
{
Considere o exemplo 5 do artigo, ele pode servir para você:
unit singleton5;
interface
uses sysutils;
type
Tsingleton5 = class
strict private
class var _Instance: Tsingleton5;
class var _PodeDestruir: boolean;
FCreationTimeStamp: string;
class destructor Destroy;
class constructor Create;
public
class function GetInstance: Tsingleton5;
class function NewInstance: TObject; override;
procedure FreeInstance; override;
constructor Create;
function DoSomethingImportant: string;
end;
implementation
class function Tsingleton5.GetInstance: Tsingleton5;
begin
if Tsingleton5._Instance = nil then
Tsingleton5._Instance := TSingleton5.Create;
Result := Tsingleton5._Instance;
end;
class constructor Tsingleton5.Create;
begin
Tsingleton5._PodeDestruir := false;
end;
class destructor Tsingleton5.Destroy;
begin
Tsingleton5._PodeDestruir := true;
if Tsingleton5._Instance <> nil then
Tsingleton5._Instance.Destroy;
end;
class function Tsingleton5.NewInstance: TObject;
begin
if _Instance = nil then
_Instance := (inherited NewInstance as Tsingleton5);
Result := _Instance;
end;
constructor Tsingleton5.Create;
begin
FCreationTimeStamp:= DateTimeToStr(now);
end;
function Tsingleton5.DoSomethingImportant: string;
begin
Result := 'Instância de ' + Self.ClassName + ' Criada em ' +
FCreationTimeStamp + ' no endereço ' + IntToStr(integer(self));
end;
procedure Tsingleton5.FreeInstance;
begin
//destruirá apenas quando for permitido
if _PodeDestruir then
begin
inherited FreeInstance;
_Instance := nil;
end;
end;
end.
}
e usar como no exemplo 5 :)
Space do autor


0
0
