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?

Foi útil?

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:

  1. Dica nº 17:Variáveis ​​de classe virtual, Parte I
  2. 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top