Isso é muito útil, por exemplo, quando lidamos com factory, pois a factory não precisa ser instanciada a todo momento. Outros exemplos são Pool de Conexões, Registry, Logs, ou recursos que sejam caros de instanciar a cada uso.
Contudo, há sempre muita controvérsia em torno da utilização do pattern singleton, isto porque ele é frequentemente mau empregado na solução dos problemas, fazendo com que o mesmo se torne apenas uma "variável global" e isso pode indicar que você tem um design pobre.
Há pelo menos duas formas diferentes em delphi de implementar este padrão. Primeiro vou mostrar como implementar utilizando uma classe comum, que possui um método estático - GetInstance() - que retorna a instância da classe.
type
TSingleton = class
private
FCanFree: Boolean;
class procedure InternalFree;
public
procedure FacaAlgo;
class function GetInstance: TSingleton;
class function NewInstance: TObject; override;
procedure FreeInstance; override;
end;
implementation
//Variável para armazenar a instância do Singleton
var
_Singleton: TSingleton = nil;
{ TSingleton }
//Lógica de negócio
procedure TSingleton.FacaAlgo;
begin
if IsConsole then
Writeln(ClassName, ' Fazendo algo')
else
ShowMessageFmt('%s Fazendo algo',[ClassName]);
end;
//Indica que o objeto pode ser destruído
class procedure TSingleton.InternalFree;
begin
if Assigned(_Singleton) then
begin
_Singleton.FCanFree := True;
_Singleton.Free;
end;
end;
//Só libera a instância quando o método InternalFree é chamado
procedure TSingleton.FreeInstance;
begin
if FCanFree then
begin
inherited;
end;
end;
//Na verdade o create vai invocar o método NewInstance indiretamente
class function TSingleton.GetInstance: TSingleton;
begin
Result := TSingleton.Create;
end;
//Instanciação Preguiçosa - Só instancia o objeto quando alguém precisar
class function TSingleton.NewInstance: TObject;
begin
if (_Singleton = nil) then
_Singleton := TSingleton(inherited NewInstance);
Result := _Singleton;
end;
initialization
finalization
TSingleton.InternalFree;
Essa abordagem é relativamente simples, mais traz consigo alguns problemas em relação a deixar o código claro. O singleton não respeita o "contrato" descrito por seu métodos, isto é, quando eu chamo o método create, ele não cria um novo objeto e quando eu invoco o método Free, ele não destroy o objeto. Isso pode causar uma certa confusão na utilização do mesmo.
Vamos dar uma olhada em um código de teste para garantir que a instância é única.
type
TTestSingleton = class(TTestCase)
private
published
procedure GetInstanceDeveRetornarUmaInstancia;
procedure GetInstanceECreateDeveRetornarAMesmaInstancia;
procedure FreeNaoPodeDestruirInstancia;
procedure DestroyNaoPodeDestruirInstancia;
procedure FreeInstanceNaoPodeDestruirInstancia;
end;
{ TTestSingleton }
procedure TTestSingleton.DestroyNaoPodeDestruirInstancia;
var
vInstance,
vInstanceAfterFree: TSingleton;
begin
vInstance := TSingleton.GetInstance;
vInstance.Destroy; //Não tem efeito
vInstanceAfterFree := TSingleton.GetInstance;
CheckTrue(vInstance = vInstanceAfterFree);
end;
procedure TTestSingleton.FreeInstanceNaoPodeDestruirInstancia;
var
vInstance,
vInstanceAfterFree: TSingleton;
begin
vInstance := TSingleton.GetInstance;
vInstance.FreeInstance; //Não tem efeito
vInstanceAfterFree := TSingleton.GetInstance;
CheckTrue(vInstance = vInstanceAfterFree);
end;
procedure TTestSingleton.FreeNaoPodeDestruirInstancia;
var
vInstance,
vInstanceAfterFree: TSingleton;
begin
vInstance := TSingleton.GetInstance;
vInstance.Free; //Não tem efeito
vInstanceAfterFree := TSingleton.GetInstance;
CheckTrue(vInstance = vInstanceAfterFree);
end;
procedure TTestSingleton.GetInstanceDeveRetornarUmaInstancia;
var
vInstance: TSingleton;
begin
vInstance := TSingleton.GetInstance;
CheckNotNull(vInstance, 'Retornou nil');
CheckTrue(vInstance is TSingleton, 'Não é um TSingleton');
vInstance.FacaAlgo;
end;
procedure TTestSingleton.GetInstanceECreateDeveRetornarAMesmaInstancia;
var
vFromGetInstance,
vFromCreate: TObject;
begin
vFromGetInstance := TSingleton.GetInstance;
vFromCreate := TSingleton.Create; //Retorna a mesma instancia
CheckTrue(vFromGetInstance = vFromCreate);
vFromCreate := TSingleton.NewInstance; //Retorna a mesma instancia
CheckTrue(vFromGetInstance = vFromCreate);
end;
No próximo post vou mostrar o segundo tipo de implementação, utilizando interfaces.
Por hoje era isso, até a próxima e usem o Singleton com moderação.
Nenhum comentário:
Postar um comentário