Constantes de classe/estáticas em Delphi
-
09-06-2019 - |
Pergunta
No Delphi, quero poder criar um objeto privado associado a uma classe e acessá-lo de todas as instâncias dessa classe.Em Java, eu usaria:
public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}
Ou, se MySharedObject precisasse de uma inicialização mais complicada, em Java eu poderia instanciar e inicializá-lo em um bloco inicializador estático.
(Você deve ter adivinhado...Eu conheço meu Java, mas sou novo no Delphi...)
De qualquer forma, não quero instanciar um novo MySharedObject cada vez que crio uma instância de MyObject, mas quero que um MySharedObject seja acessível a partir de cada instância de MyObject.(Na verdade, foi o registro que me estimulou a tentar descobrir isso - estou usando o Log4D e quero armazenar um TLogLogger como uma variável de classe para cada classe que possui funcionalidade de registro.)
Qual é a maneira mais legal de fazer algo assim no Delphi?
Solução
Aqui está como farei isso usando uma variável de classe, um procedimento de classe e um bloco de inicialização:
unit MyObject;
interface
type
TMyObject = class
private
class var FLogger : TLogLogger;
public
class procedure SetLogger(value:TLogLogger);
class procedure FreeLogger;
end;
implementation
class procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
class procedure TMyObject.FreeLogger;
begin
if assigned(FLogger) then
FLogger.Free;
end;
initialization
TMyObject.SetLogger(TLogLogger.Create);
finalization
TMyObject.FreeLogger;
end.
Outras dicas
TMyObject = class
private
class var FLogger : TLogLogger;
procedure SetLogger(value:TLogLogger);
property Logger : TLogLogger read FLogger write SetLogger;
end;
procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
Observe que esta variável de classe poderá ser escrita a partir de qualquer instância de classe, portanto, você pode configurá-la em algum outro lugar do código, geralmente com base em alguma condição (tipo de criador de logs, etc.).
Editar:Também será o mesmo em todos os descendentes da classe.Altere-o em um dos filhos e ele será alterado para todas as instâncias descendentes.Você também pode configurar o tratamento de instância padrão.
TMyObject = class
private
class var FLogger : TLogLogger;
procedure SetLogger(value:TLogLogger);
function GetLogger:TLogLogger;
property Logger : TLogLogger read GetLogger write SetLogger;
end;
function TMyObject.GetLogger:TLogLogger;
begin
if not Assigned(FLogger)
then FLogger := TSomeLogLoggerClass.Create;
Result := FLogger;
end;
procedure TMyObject.SetLogger(value:TLogLogger);
begin
// sanity checks here
FLogger := Value;
end;
No ano passado, Hallvard Vassbotn blogou sobre um hack Delphi que fiz para isso, que se tornou um artigo de duas partes:
- Dica nº 17:Variáveis de classe virtual, Parte I
- Dica nº 17:Variáveis de classe virtual, Parte II
Sim, é uma leitura longa, mas muito gratificante.
Em resumo, reutilizei a entrada VMT (obsoleta) chamada vmtAutoTable como uma variável.Este slot no VMT pode ser usado para armazenar qualquer valor de 4 bytes, mas se você quiser armazenar, você sempre pode alocar um registro com todos os campos que desejar.
As palavras-chave que você está procurando são "class var" - isso inicia um bloco de variáveis de classe na sua declaração de classe.Você precisa terminar o bloco com "var" se desejar incluir outros campos depois dele (caso contrário, o bloco pode ser finalizado por um especificador "privado", "público", "procedimento" etc.).Por exemplo
(Editar:Reli a pergunta e movi a contagem de referências para TMyClass - pois talvez você não consiga editar a classe TMySharedObjectClass que deseja compartilhar, se ela vier da biblioteca de outra pessoa)
TMyClass = class(TObject)
strict private
class var
FMySharedObjectRefCount: integer;
FMySharedObject: TMySharedObjectClass;
var
FOtherNonClassField1: integer;
function GetMySharedObject: TMySharedObjectClass;
public
constructor Create;
destructor Destroy; override;
property MySharedObject: TMySharedObjectClass read GetMySharedObject;
end;
{ TMyClass }
constructor TMyClass.Create;
begin
if not Assigned(FMySharedObject) then
FMySharedObject := TMySharedObjectClass.Create;
Inc(FMySharedObjectRefCount);
end;
destructor TMyClass.Destroy;
begin
Dec(FMySharedObjectRefCount);
if (FMySharedObjectRefCount < 1) then
FreeAndNil(FMySharedObject);
inherited;
end;
function TMyClass.GetMySharedObject: TMySharedObjectClass;
begin
Result := FMySharedObject;
end;
Observe que o texto acima não é seguro para threads e pode haver maneiras melhores de contar referências (como usar interfaces), mas este é um exemplo simples que deve ajudá-lo a começar.Observe que TMySharedObjectClass pode ser substituído por TLogLogger ou o que você quiser.
Bem, não é bonito, mas funciona bem no Delphi 7:
TMyObject = class
pulic
class function MySharedObject: TMySharedObject; // I'm lazy so it will be read only
end;
implementation
...
class function MySharedObject: TMySharedObject;
{$J+} const MySharedObjectInstance: TMySharedObject = nil; {$J-} // {$J+} Makes the consts writable
begin
// any conditional initialization ...
if (not Assigned(MySharedObjectInstance)) then
MySharedObjectInstance = TMySharedOject.Create(...);
Result := MySharedObjectInstance;
end;
Atualmente estou usando-o para construir objetos singletons.
Para o que eu quero fazer (uma constante de classe privada), a solução mais simples que posso encontrar (com base nas respostas até agora) é:
unit MyObject;
interface
type
TMyObject = class
private
class var FLogger: TLogLogger;
end;
implementation
initialization
TMyObject.FLogger:= TLogLogger.GetLogger(TMyObject);
finalization
// You'd typically want to free the class objects in the finalization block, but
// TLogLoggers are actually managed by Log4D.
end.
Talvez um pouco mais orientado a objetos seria algo como:
unit MyObject;
interface
type
TMyObject = class
strict private
class var FLogger: TLogLogger;
private
class procedure InitClass;
class procedure FreeClass;
end;
implementation
class procedure TMyObject.InitClass;
begin
FLogger:= TLogLogger.GetLogger(TMyObject);
end;
class procedure TMyObject.FreeClass;
begin
// Nothing to do here for a TLogLogger - it's freed by Log4D.
end;
initialization
TMyObject.InitClass;
finalization
TMyObject.FreeClass;
end.
Isso poderia fazer mais sentido se houvesse várias constantes de classe.
Acho que duas perguntas precisam ser respondidas antes de você encontrar uma solução "perfeita".
- A primeira é se o TLogLogger é thread-safe.O mesmo TLogLogger pode ser chamado de vários threads? sem chamadas para "sincronizar"?Mesmo assim, o seguinte ainda pode ser aplicado
- As variáveis de classe são thread-in-scope ou verdadeiramente globais?
- Se as variáveis de classe forem verdadeiramente globais e o TLogLogger não for thread-safe, talvez seja melhor usar um threadvar unit-global para armazenar o TLogLogger (por mais que eu não goste de usar vars "globais" de qualquer forma), por exemplo
Código:
interface
type
TMyObject = class(TObject)
private
FLogger: TLogLogger; //NB: pointer to shared threadvar
public
constructor Create;
end;
implementation
threadvar threadGlobalLogger: TLogLogger = nil;
constructor TMyObject.Create;
begin
if not Assigned(threadGlobalLogger) then
threadGlobalLogger := TLogLogger.GetLogger(TMyObject); //NB: No need to reference count or explicitly free, as it's freed by Log4D
FLogger := threadGlobalLogger;
end;
Editar:Parece que as variáveis de classe são armazenadas globalmente, em vez de uma instância por thread.Ver essa questão para detalhes.
No Delphi, variáveis estáticas são implementadas como constantes de tipos de variáveis :)
Isso pode ser um tanto enganador.
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
E sim, outra possibilidade é usar variável global em implementation
parte do seu módulo.
Isso só funciona se a opção do compilador "Assignable Consts" estiver ativada, globalmente ou com {$J+}
sintaxe (tnx Lars).
Antes da versão 7, o Delphi não tinha variáveis estáticas, você teria que usar uma variável global.
Para torná-lo o mais privado possível, coloque-o no implementation
seção de sua unidade.