Constantes de classe / statique dans Delphi
-
09-06-2019 - |
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?
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:
- Hack n ° 17: variables de classe virtuelle Partie I
- 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é.