Klasse / Statische Konstanten in Delphi
-
09-06-2019 - |
Frage
In Delphi, Ich will ein privates Objekt erstellen können, die mit einer Klasse zugeordnet ist und Zugriff auf sie von allen Instanzen dieser Klasse. In Java, würde ich verwenden:
public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
}
Oder, wenn MySharedObject komplizierte Initialisierung benötigt, in Java konnte ich es in einem statischen Initialisierungsblocks instanziiert und initialisiert wird.
(Sie ahnen es ... Ich weiß, dass mein Java, aber ich bin ziemlich neu in Delphi ...)
Wie auch immer, ich will keine neues MySharedObject jedes Mal, wenn ich eine Instanz von MyObject schaffen instanziiert, aber ich ein MySharedObject will von jeder Instanz von MyObject zugänglich sein. (Es ist eigentlich die Protokollierung das hat mich angespornt, um zu versuchen, das herauszufinden. - Ich bin mit Log4D und ich mag für jede Klasse eine TLogLogger als Klassenvariable speichern, die Funktionalität Protokollierung)
Was ist der sauberste Weg, so etwas wie dies in Delphi zu tun?
Lösung
Hier ist, wie ich tun werde, dass eine Klassenvariable, eine Klasse Verfahren und eine Initialisierungsbaustein mit:
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.
Andere Tipps
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;
Beachten Sie, dass dieser Klassenvariable aus jeder Klasse Instanz beschreibbar sein wird, daher können Sie es irgendwo anders im Code festgelegt, in der Regel basierend auf einer Bedingung (Art des Logger usw.).
Edit: Es wird auch das gleiche in allen Nachkommen der Klasse sein. Ändern Sie es in einem der Kinder, und es ändert sich für alle Nachkommen Instanzen. Sie können auch Standardinstanz Handhabung einrichten.
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;
Im vergangenen Jahr Hallvard Vassbotn über einen Delphi-Hack gebloggt ich dafür gemacht hatte, wurde ein zweiteiliger Artikel:
Ja, es ist eine lange lesen, aber sehr lohnend.
Insgesamt habe ich wieder verwendet den (veraltet) VMT Eintrag vmtAutoTable als Variable bezeichnet. Dieser Schlitz in der VMT verwendet werden, kann eine beliebige 4-Byte-Wert zu speichern, aber wenn Sie speichern möchten, könnten Sie immer einen Datensatz mit allen Feldern zuordnen Sie wünschen kann.
Die Keywords, die Sie suchen sind „class var“ - so beginnt ein Block von Klassenvariablen in der Klassendeklaration. Sie müssen den Block mit „var“ beenden, wenn Sie möchten, andere Felder enthalten, nachdem sie (sonst kann der Block durch eine „private“, „public“, „Verfahren“ etc Spezifizierer beendet werden). Eg
(Edit: Ich lese die Frage und bewegte Referenzzähler in TMyClass - wie Sie nicht in der Lage sein können, die TMySharedObjectClass Klasse, die Sie teilen mögen, zu bearbeiten, wenn es von jemandem anderer Bibliothek kommt)
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;
Bitte beachten Sie die oben ist nicht Thread-sicher, und es können bessere Möglichkeiten der Referenzzählung sein (wie zB Schnittstellen), aber dies ist ein einfaches Beispiel, das Sie begonnen erhalten sollen. Notieren Sie sich die TMySharedObjectClass kann durch TLogLogger ersetzt werden oder was auch immer Sie mögen.
Nun, es ist nicht Schönheit, sondern funktioniert 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;
Ich bin mit curently es Singletons Objekte zu bauen.
Für das, was ich (eine private Klasse konstant) tun will, die sauberste Lösung, die ich mit oben kommen kann (basierend auf den Antworten bisher) ist:
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.
Vielleicht ein wenig mehr objektorientierte würde wie etwas sein:
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.
Das könnte mehr Sinn machen, wenn es mehr solche Klassenkonstanten waren.
Zwei Fragen denke ich, dass müssen beantwortet werden, bevor Sie mit einer „perfekten“ Lösung zu kommen ..
- Die erste ist, ob TLogLogger ist Thread-sicher. Kann das gleiche TLogLogger aufgerufen werden von mehreren Threads ohne Anrufe auf „Synchronize“? Selbst wenn dies der Fall, kann folgendes gelten weiterhin
- Sind Klassenvariablen Thread-in-Bereich oder wirklich global?
- Wenn Klassenvariablen wirklich global sind, und TLogLogger ist nicht Thread-sicher, dass Sie vielleicht am besten mit einem anteil globalem threadvar verwenden, um die TLogLogger zu speichern (so viel wie Ich mag es nicht „global“ Vars in irgendeiner Form verwenden) , zB
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: Es scheint, dass Klassenvariablen global gespeichert werden, anstatt eine Instanz pro Thread. Siehe diese Frage .
In Delphi statischen Variablen sind implementiert als Variablentypen Konstanten :)
Dies könnte etwas irreführend sein.
procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end;
Und ja, ein andere Möglichkeit globalen Variable in implementation
Teil des Moduls verwendet wird.
Dies funktioniert nur, wenn der Compiler-Schalter "zuweisbare consts" eingeschaltet ist, global oder mit {$J+}
Syntax (tnx Lars).
Vor der Version 7, Delphi keine statischen Variablen haben, würden Sie haben eine globale Variable verwenden.
Um es so privat wie möglich zu machen, es in dem implementation
Abschnitt des Geräts setzen.