A criação de um singleton em Delphi utilizando os novos recursos do D2009 e D2010
-
05-07-2019 - |
Pergunta
Eu estou olhando para criar um singleton em Delphi.Eu já fiz isso antes usando versões antigas do Delphi, e acabou usando variáveis globais (na seção de implementação) e, usando a inicialização e finalização para cuidar da instância.Também não havia nenhuma maneira de impedir que o usuário a criação de uma instância como você não poderia esconder o construtor padrão.Eu queria saber se qualquer uma das novas funcionalidades tais como a classe de construtores e destrutores, e variáveis de classe (ok, não tão nova), talvez genéricos, poderia ajudar na criação de um genérico classe singleton.Eu ainda não conseguiu criar algo para a minha satisfação ainda.
Solução
Era possível gerenciar isso substituindo o VERDADEIRO atribuição e deallocator métodos em Delphi, NewInstance e FreeInstance.Construtores e Destrutores em Delphi apenas inicializar e finalizar respectivamente, eles não alocar ou desalocar a memória, então, tentar ocultar os construtores sempre foi um pouco equivocado.
i.e.foi possível, para permitir a utilização de qualquer e todos os construtores contanto que você derrubou NewInstance de tal forma que ele só retornou uma referência a uma única alocação de memória para a classe.
Mas a tentativa de impor um uso/padrão de comportamento em uma classe base é um erro imho.Nem TODOS os padrões são ou exigir classes específicas para encapsular o padrão.
Em casos como este, você acaba criando algo que é desnecessariamente complicado, e a complicação atrai erros na minha experiência e o objeto do exercício torna-se então tentar encontrar falhas na implementação do padrão e, em seguida, tentar implementar salvaguardas contra essas falhas, ao invés de ficar com o trabalho na prática de trabalho que a classe singleton era suposto para executar.
É muito, MUITO mais simples e mais eficaz para documentar o USO da classe.
A documentação como uma técnica para implementar este padrão tem funcionou perfeitamente durante 15 anos para o Aplicação e Tela objetos da VCL, por exemplo, para não falar de inúmeras outras gestações únicas que eu criei naqueles anos.
Outras dicas
Se você só precisa de uma simples singleton, a maneira mais simples é usar construtores de classe e métodos de classe, como sugerido por plainth.Mas os genéricos são muito úteis se você precisar de gestações únicas com a construção da demanda (por exemplo,no primeiro acesso).
O código a seguir é tirada de um dos meus utilitário de unidades;basicamente, ele oferece um genérico singleton fábrica para o Delphi 2009.
interface
type
{$HINTS OFF}
{ TSingletonInstance<> implements lazy creation, which is sometimes useful for avoiding
expensive initialization operations.
If you do not require lazy creation and you target only Delphi 2010 onwards, you should
use class constructors and class destructors instead to implement singletons. }
TSingletonInstance<T: class, constructor> = record
private
FGuard: IInterface;
FInstance: T;
function GetInstance: T;
function CreateInstance: TObject;
public
property Instance: T read GetInstance;
end;
{$HINTS ON}
TSingletonFactoryFunction = function: TObject of object;
{ Private symbols (which are in the interface section because of known limitations of generics) }
procedure _AllocateSingletonInstance (InstanceRecord: Pointer; Factory: TSingletonFactoryFunction);
implementation
{ TSingleton }
var
SingletonCriticalSection: TRTLCriticalSection;
type
TSingletonGuard = class (TInterfacedObject)
private
FSingletonInstance: TObject;
public
constructor Create (AInstance: TObject);
destructor Destroy; override;
end;
PUntypedSingletonInstance = ^TUntypedSingletonInstance;
TUntypedSingletonInstance = record
FGuard: IInterface;
FInstance: TObject;
end;
// TODO: is a lock required for multiple threads accessing a single interface variable?
procedure _AllocateSingletonInstance (InstanceRecord: Pointer; Factory: TSingletonFactoryFunction);
var
USI: PUntypedSingletonInstance;
begin
USI := PUntypedSingletonInstance (InstanceRecord);
EnterCriticalSection (SingletonCriticalSection);
if USI.FInstance = nil then
begin
USI.FInstance := Factory ();
USI.FGuard := TSingletonGuard.Create (USI.FInstance);
end;
LeaveCriticalSection (SingletonCriticalSection);
end;
constructor TSingletonGuard.Create (AInstance: TObject);
begin
FSingletonInstance := AInstance;
end;
destructor TSingletonGuard.Destroy;
begin
FSingletonInstance.Free;
inherited;
end;
function TSingletonInstance<T>.GetInstance: T;
var
Factory: TSingletonFactoryFunction;
begin
if FInstance = nil then
begin
Factory := Self.CreateInstance; // TODO: associate QC report
_AllocateSingletonInstance (@Self, Factory);
end;
Result := FInstance;
end;
function TSingletonInstance<T>.CreateInstance: TObject;
begin
Result := T.Create;
end;
initialization
InitializeCriticalSection (SingletonCriticalSection);
finalization
DeleteCriticalSection (SingletonCriticalSection);
O uso da seguinte forma:
type
TMySingleton = class
public
constructor Create;
class function Get: TMySingleton; static;
end;
var
MySingletonInstance: TSingletonInstance<TMySingleton>;
class function TMySingleton.Get: TMySingleton;
begin
Result := MySingletonInstance.Instance;
end;
No Delphi 2010, de longe, a melhor e mais segura maneira é usar construtores de classe.Ver aqui - leia especialmente o parágrafo chamado Melhorado o encapsulamento.
HTH.
Eu prefiro usar interfaces quando eu preciso de gestações únicas e ocultar a implementação da interface na seção de implementação.
benefícios
- Destruição automática quando o programa termina.
- De jeito nenhum acidentalmente a criar um TMySingleton.
desvantagens
- Alguém pode decidir implementar IMySingleton no seu próprio.
Nota:Eu acredito que o uso de Singletons deve ser mantido a um mínimo absoluto.Tudo em tudo, Singletons são pouco mais do que glorificaram a variáveis globais.Se e quando você começa a unidade de testar o seu código, eles se tornam um incômodo.
unit uSingleton;
interface
type
ISingleton = interface
['{8A449E4B-DEF9-400E-9C21-93DFA2D5F662}']
end;
function Singleton: ISingleton;
implementation
uses
SyncObjs;
type
TSingleton = class(TInterfacedObject, ISingleton);
var
Lock: TCriticalSection;
function Singleton: ISingleton;
const
_singleton: ISingleton = nil;
begin
if not Assigned(_singleton) then
begin
Lock.Acquire;
try
if not Assigned(_singleton) then
_singleton := TSingleton.Create();
finally
Lock.Release;
end;
end;
Result := _singleton;
end;
initialization
Lock := TCriticalSection.Create;
finalization
Lock.Free;
end.
Há uma forma de ocultar a herança "Criar" construtor de TObject.Embora não seja possível alterar o nível de acesso, ele pode ser escondido com outro público sem parâmetros método com o mesmo nome:"Criar".Isso simplifica a implementação da classe Singleton tremendamente.Veja a simplicidade do código:
unit Singleton;
interface
type
TSingleton = class
private
class var _instance: TSingleton;
public
//Global point of access to the unique instance
class function Create: TSingleton;
destructor Destroy; override;
end;
implementation
{ TSingleton }
class function TSingleton.Create: TSingleton;
begin
if (_instance = nil) then
_instance:= inherited Create as Self;
result:= _instance;
end;
destructor TSingleton.Destroy;
begin
_instance:= nil;
inherited;
end;
end.
Eu adicionei os detalhes para o meu post original: http://www.yanniel.info/2010/10/singleton-pattern-delphi.html
Para um singleton, você pode substituir o método NewInstance.E usar uma variável de classe.Você cria a variável em primeira chamada e retornar o ponteiro para a classe de cada chamada.
Você só precisa encontrar someting para destruí-lo no final (provavelmente usando a finalizar).
Eu prefiro criar classe singleton usando um gerador de código.O problema com o genérico é a de que, todo o código é gerado na memória, não no arquivo de origem.Ele irá aumentar a dificuldade de depuração.