在Delphi中,我希望能够创建与类相关联的私有对象,并从该类的所有实例中访问它。 在Java中,我将使用:
1 2 3
| public class MyObject {
private static final MySharedObject mySharedObjectInstance = new MySharedObject();
} |
或者,如果MySharedObject需要更复杂的初始化,那么在Java中,我可以在静态初始化程序块中实例化和初始化它。
(您可能已经猜到了……我知道我的Java语言,但我对Delphi还是陌生的。)
无论如何,我不想每次创建MyObject实例时都实例化一个新的MySharedObject,但我希望可以从MyObject的每个实例访问MySharedObject。 (实际上是日志记录,这促使我试图找出答案-我正在使用Log4D,并且我想将TLogLogger存储为具有日志记录功能的每个类的类变量。)
在Delphi中做这种事情的最巧妙的方法是什么?
这是使用类变量,类过程和初始化块的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| 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. |
去年,Hallvard Vassbotn在博客中介绍了我为此编写的Delphi-hack,该文章分为两部分:
Hack#17:虚拟类变量,第一部分
Hack#17:虚拟类变量,第二部分
是的,这是一本很长的书,但很有收获。
总而言之,我将(已弃用的)称为vmtAutoTable的VMT条目作为变量重用。
VMT中的该插槽可用于存储任何4字节的值,但如果要存储,则始终可以分配一条记录,其中包含所有希望的字段。
1 2 3 4 5 6 7 8 9 10 11 12
| 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; |
请注意,该类变量可以从任何类实例中写入,因此您通常可以根据某些条件(记录器的类型等)将其设置在代码的其他位置。
编辑:在该类的所有子代中也将相同。在其中一个子项中更改它,并且它对于所有后代实例都更改。
您还可以设置默认实例处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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; |
好吧,这不是美,但是在Delphi 7中可以正常工作:
1 2 3 4 5 6
| TMyObject = class
pulic
class function MySharedObject: TMySharedObject; // I'm lazy so it will be read only
end;
implementation |
...
1 2 3 4 5 6 7 8
| 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; |
我目前正在使用它来构建单例对象。
您要查找的关键字是" class var"-这将在您的类声明中启动一个类变量。如果希望在块之后添加其他字段,则需要以" var"结尾(否则,该块可以由" private"," public"," procedure"等说明符结束)。例如
(编辑:我重新阅读了问题并将引用计数移入TMyClass-因为您可能无法编辑要共享的TMySharedObjectClass类,如果它来自其他人的库)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| 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; |
请注意,上面的方法不是线程安全的,并且可能有更好的引用计数方法(例如使用接口),但这是一个简单的示例,应该可以帮助您入门。注意,TMySharedObjectClass可以用TLogLogger或任何您喜欢的东西代替。
在提出"完美"的解决方案之前,我认为需要回答两个问题。
-
首先,TLogLogger是否是线程安全的。是否可以从多个线程中调用同一TLogLogger而无需调用" syncronize"?即使是这样,以下内容仍然可能适用
-
类变量是范围内线程还是真正的全局变量?
-
如果类变量确实是全局变量,并且TLogLogger不是线程安全的,则可能最好使用单位全局线程变量来存储TLogLogger(就像我不喜欢以任何形式使用"全局"变量)一样,例如
码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| 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; |
编辑:似乎类变量是全局存储的,而不是每个线程一个实例。有关详细信息,请参见此问题。
对于我想做的事情(一个私有类常量),我可以想出的最简洁的解决方案(根据到目前为止的回答)是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| 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. |
也许更多的面向对象将是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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. |
如果存在多个此类常量,那可能更有意义。
在Delphi中,静态变量实现为变量类型常量:)
这可能会产生误导。
1 2 3 4 5 6 7
| procedure TForm1.Button1Click(Sender: TObject) ;
const
clicks : Integer = 1; //not a true constant
begin
Form1.Caption := IntToStr(clicks) ;
clicks := clicks + 1;
end; |
是的,另一种可能性是在模块的implementation部分中使用全局变量。
这仅在全局或使用{$J+}语法(tnx Lars)打开编译器开关" Assignable Consts"时有效。
在版本7之前,Delphi没有静态变量,您必须使用全局变量。
要使其尽可能私密,请将其放在设备的implementation部分中。