Domanda

In Delphi, voglio essere in grado di creare un oggetto privato associato a una classe e accedervi da tutte le istanze di quella classe.In Java, utilizzerei:

public class MyObject {
    private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}

Oppure, se MySharedObject avesse bisogno di un'inizializzazione più complicata, in Java potrei istanziarlo e inizializzarlo in un blocco inizializzatore statico.

(Potresti aver indovinato...Conosco Java ma sono piuttosto nuovo a Delphi...)

Ad ogni modo, non voglio istanziare un nuovo MySharedObject ogni volta che creo un'istanza di MyObject, ma voglio che un MySharedObject sia accessibile da ogni istanza di MyObject.(In realtà è la registrazione che mi ha spinto a provare a capirlo: sto usando Log4D e voglio memorizzare un TLogLogger come variabile di classe per ogni classe che ha funzionalità di registrazione.)

Qual è il modo più semplice per fare qualcosa del genere in Delphi?

È stato utile?

Soluzione

Ecco come lo farò utilizzando una variabile di classe, una procedura di classe e un blocco di inizializzazione:

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.

Altri suggerimenti

 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;

Tieni presente che questa variabile di classe sarà scrivibile da qualsiasi istanza di classe, quindi puoi impostarla da qualche altra parte nel codice, solitamente in base ad alcune condizioni (tipo di logger ecc.).

Modificare:Sarà lo stesso anche in tutti i discendenti della classe.Modificatelo in uno dei figli e cambierà per tutte le istanze discendenti.Puoi anche impostare la gestione predefinita delle istanze.

 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;

L'anno scorso, Hallvard Vassbotn ha scritto sul blog di un hack Delphi che avevo realizzato per questo scopo, è diventato un articolo in due parti:

  1. Trucco#17:Variabili di classe virtuale, parte I
  2. Trucco#17:Variabili di classe virtuale, Parte II

Sì, è una lettura lunga, ma molto gratificante.

In sintesi, ho riutilizzato la voce VMT (obsoleta) denominata vmtAutoTable come variabile.Questo slot nel VMT può essere utilizzato per memorizzare qualsiasi valore di 4 byte, ma se vuoi memorizzarlo, puoi sempre allocare un record con tutti i campi che desideri.

Le parole chiave che stai cercando sono "class var": avvia un blocco di variabili di classe nella dichiarazione della classe.È necessario terminare il blocco con "var" se si desidera includere altri campi dopo di esso (altrimenti il ​​blocco potrebbe essere terminato con uno specificatore "private", "public", "procedure" ecc.).Per esempio

(Modificare:Ho riletto la domanda e spostato il conteggio dei riferimenti in TMyClass, poiché potresti non essere in grado di modificare la classe TMySharedObjectClass che desideri condividere, se proviene dalla libreria di qualcun altro)

  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;

Tieni presente che quanto sopra non è thread-safe e potrebbero esserci modi migliori per contare i riferimenti (come utilizzare le interfacce), ma questo è un semplice esempio che dovrebbe aiutarti a iniziare.Nota che TMySharedObjectClass può essere sostituito da TLogLogger o qualunque cosa tu voglia.

Beh, non è bello, ma funziona bene in 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;

Attualmente lo sto usando per costruire oggetti singleton.

Per quello che voglio fare (una costante di classe privata), la soluzione più accurata che riesco a trovare (basata sulle risposte finora) è:

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.

Forse un po' più orientato agli oggetti sarebbe qualcosa del tipo:

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.

Ciò potrebbe avere più senso se esistessero più costanti di classe di questo tipo.

Penso che sia necessario rispondere a due domande prima di trovare una soluzione "perfetta".

  • Il primo è se TLogLogger è thread-safe.Lo stesso TLogLogger può essere chiamato da più thread senza chiamate a "sincronizzare"?Anche se così, potrebbe comunque applicarsi quanto segue
  • Le variabili di classe sono thread-in-scope o veramente globali?
  • Se le variabili di classe sono veramente globali e TLogLogger non è thread-safe, potrebbe essere meglio utilizzare una threadvar globale per unità per memorizzare TLogLogger (per quanto non mi piaccia usare variabili "globali" in qualsiasi forma), ad es.

Codice:

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;

Modificare:Sembra che le variabili di classe siano archiviate a livello globale, anziché un'istanza per thread.Vedere questa domanda per dettagli.

In Delphi le variabili statiche sono implementate come costanti dei tipi di variabile :)

Questo potrebbe essere in qualche modo fuorviante.

procedure TForm1.Button1Click(Sender: TObject) ;
const
   clicks : Integer = 1; //not a true constant
begin
  Form1.Caption := IntToStr(clicks) ;
  clicks := clicks + 1;
end;

E sì, un'altra possibilità è usare la variabile globale in implementation parte del tuo modulo.

Funziona solo se l'opzione del compilatore "Assignable Consts" è attivata, globalmente o con {$J+} sintassi (grazie a Lars).

Prima della versione 7, Delphi non aveva variabili statiche, dovevi usare una variabile globale.

Per renderlo il più privato possibile, inseriscilo nel file implementation sezione della vostra unità.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top