Question

Dans Delphi, je souhaite pouvoir créer un objet privé associé à une classe et y accéder à partir de toutes les instances de cette classe. En Java, j'utiliserais:

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

Ou, si MySharedObject nécessitait une initialisation plus compliquée, je pouvais l'instancier et l'initialiser dans un bloc d'initialisation statique.

(Vous avez peut-être deviné ... Je connais mon Java mais je suis plutôt nouveau dans Delphi ...)

Quoi qu'il en soit, je ne veux pas instancier un nouvel objet MyShared à chaque fois que je crée une instance de MyObject, mais je souhaite qu'un objet MySharedObject soit accessible à partir de chaque instance de MyObject. (C'est en fait la journalisation qui m'a poussé à essayer de comprendre cela: j'utilise Log4D et je souhaite stocker un TLogLogger en tant que variable de classe pour chaque classe dotée d'une fonctionnalité de journalisation.)

Quel est le meilleur moyen de faire quelque chose comme ça à Delphi?

Était-ce utile?

La solution

Voici comment je vais le faire en utilisant une variable de classe, une procédure de classe et un bloc d'initialisation:

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.

Autres conseils

 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;

Notez que cette variable de classe sera accessible en écriture à partir de n'importe quelle instance de classe. Vous pouvez donc la définir ailleurs dans le code, généralement en fonction d'une condition (type de consignateur, etc.).

Éditer: Ce sera également le même chez tous les descendants de la classe. Changez-le dans l'un des enfants et cela changera pour toutes les instances descendantes. Vous pouvez également configurer la gestion des instances par défaut.

 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'année dernière, Hallvard Vassbotn a publié un article sur un hack de Delphi que j'avais créé pour cela, il est devenu un article en deux parties:

  1. Hack n ° 17: variables de classe virtuelle Partie I
  2. Hack n ° 17: variables de classe virtuelle , Partie II

Oui, c'est une lecture longue, mais très enrichissante.

En résumé, j'ai réutilisé l'entrée VMT (obsolète) appelée vmtAutoTable en tant que variable. Cet emplacement de la machine VMT peut être utilisé pour stocker toute valeur de 4 octets, mais si vous souhaitez le stocker, vous pouvez toujours allouer un enregistrement avec tous les champs souhaités.

Les mots clés que vous recherchez sont les suivants: "class var". - cela commence un bloc de variables de classe dans votre déclaration de classe. Vous devez terminer le bloc avec " var " si vous souhaitez inclure d'autres champs après (sinon le bloc peut être terminé par un spécificateur "privé", "public", "procédure", etc.). P.ex.

(Édition: je relis la question et déplace le nombre de références dans TMyClass - vous ne pourrez peut-être pas modifier la classe TMySharedObjectClass que vous souhaitez partager, si elle provient de la bibliothèque de quelqu'un d'autre)

  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;

Veuillez noter que ce qui précède n'est pas thread-safe et qu'il existe peut-être de meilleures méthodes de comptage de références (telles que l'utilisation d'interfaces), mais il s'agit d'un exemple simple qui devrait vous aider à démarrer. Notez que TMySharedObjectClass peut être remplacé par TLogLogger ou ce que vous voulez.

Eh bien, ce n'est pas de la beauté, mais cela fonctionne bien dans 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;

Je l'utilise actuellement pour construire des objets singletons.

Pour ce que je veux faire (une constante de classe privée), la solution la plus élégante que je puisse trouver (en fonction des réponses reçues jusqu'à présent) est la suivante:

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.

Peut-être un peu plus orienté objet serait quelque chose comme:

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.

Cela aurait plus de sens s'il existait plusieurs constantes de classe.

Je pense qu’il faut répondre à deux questions avant de vous proposer un "parfait". solution ..

  • La première consiste à déterminer si TLogLogger est compatible avec les threads. Le même TLogLogger peut-il être appelé à partir de plusieurs threads sans appels à "syncronize"? Même si c'est le cas, les éléments suivants peuvent toujours s'appliquer
  • Les variables de classe sont-elles intégrées dans le champ ou véritablement globales?
  • Si les variables de classe sont vraiment globales et que TLogLogger n'est pas thread-safe, vous feriez mieux d'utiliser une unité de thread threadvar globale pour stocker le TLogLogger (autant que je n'aime pas utiliser les vars "globaux" sous quelque forme que ce soit). ), par exemple

Code:

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;

Edit: Il semble que les variables de classe soient globalement stockées, plutôt qu’une instance par thread. Voir cette question pour plus de détails.

Dans Delphi, les variables statiques sont implémentées sous la forme de constantes de types de variables :)

Cela pourrait être quelque peu trompeur.

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

Et oui, une autre possibilité consiste à utiliser la variable globale dans la partie implementation de votre module.

Ceci ne fonctionne que si le compilateur commute " Assignable Consts " est activé, globalement ou avec la syntaxe {$ J +} (tnx Lars).

Avant la version 7, Delphi n’avait pas de variables statiques, vous deviez utiliser une variable globale.

Pour le rendre aussi privé que possible, placez-le dans la section implementation de votre unité.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top