两处引用接口对象
Reference to interfaced object in two pleaces
unit example;
interface
type
ILettersSettings = interface
function Letters: String;
end;
INumbersSettings = interface
function Numbers: String;
end;
TSettings = class(TInterfacedObject, ILettersSettings, INumbersSettings)
private
fLoadedLetters: String;
fLoadedNumbers: String;
public
procedure LoadFromFile;
private {ILettersSettings}
function Letters: String;
private {INumbersSettings}
function Numbers: String;
end;
TNumbers = class
private
fNumbers: String;
public
constructor Create(settings: INumbersSettings);
end;
TLetters = class
private
fLetters: String;
public
constructor Create(settings: ILettersSettings);
end;
implementation
{ TSettings }
procedure TSettings.LoadFromFile;
begin
fLoadedLetters := 'abc';
fLoadedNumbers := '123';
end;
function TSettings.Letters: String;
begin
result := fLoadedLetters;
end;
function TSettings.Numbers: String;
begin
result := fLoadedNumbers;
end;
{ TNumbers }
constructor TNumbers.Create(settings: INumbersSettings);
begin
fNumbers := settings.Numbers;
end;
{ TLetters }
constructor TLetters.Create(settings: ILettersSettings);
begin
fLetters := settings.Letters;
end;
var
settings: TSettings;
letters: TLetters;
numbers: TNumbers;
begin
settings := TSettings.Create;
settings.LoadFromFile;
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
end.
我对整个项目的设置有异议。
settings := TSettings.Create;
settings.LoadFromFile;
我用这个对象创建了两个对象:数字和字母,通过构造函数注入它。
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
但我没有将它分配给构造函数中的任何变量,只是使用它。
{ TNumbers }
constructor TNumbers.Create(settings: INumbersSettings);
begin
fNumbers := settings.Numbers;
end;
{ TLetters }
constructor TLetters.Create(settings: ILettersSettings);
begin
fLetters := settings.Letters;
end;
所以在构造函数开始时引用计数=1,在构造函数结束时引用计数减少到0,对象被销毁。
所以符合:
numbers := TNumbers.Create(settings);
注入 nil 并引发运行时错误。
如何解决?
有很多方法可以解决这个问题...最简单的方法可能是让 TSettings 继承自 TComponent 而不是 TInterfacedObject。
TComponent默认实现了IInterface但不实现引用计数,所以当引用计数减少时,对象不会被销毁。这也意味着你必须自己销毁它。
TSettings = class(TComponent, ILettersSettings, INumbersSettings)
[...]
settings := TSettings.Create;
try
settings.LoadFromFile;
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
finally
Settings.Free;
end;
问题是您混合了两种不同的生命周期管理方法。您混合了引用计数生命周期管理和程序员控制的生命周期管理。
您的变量 settings
被声明为 TSettings
类型。尽管您没有显示该声明,但我们知道这是因为您可以调用 LoadFromFile
。这只有在 settings
被声明为 TSettings
.
类型时才有可能
因为 settings
是 class,这意味着您的代码对其生命周期负责。因此,当您分配给 settings
.
时,编译器不会发出引用计数代码
但是,当您调用 TLetters.Create
和 TNumbers.Create
时,您将接口引用分别传递给 ILetters
和 INumbers
。对于此代码,编译器确实发出引用计数代码。当您获得接口引用时,引用计数增加到 1,然后当该引用离开范围时减少到零。此时执行对象被销毁。
这一切的根本问题是你违反了生命周期管理规则。您不能像您所做的那样混合使用两种不同的方法。
人们通常采用的策略是要么始终使用程序员控制的管理,要么始终使用引用计数管理。这是你的选择。
如果您希望专门使用引用计数管理,那么您需要确保设置 class 的所有功能都可以通过接口使用。这意味着确保 LoadFromFile
可以通过接口调用。或者可能安排它由构造函数调用。
或者您可以切换到程序员控制的管理。在那种情况下,您不能从 TInterfacedObject
派生。您可能会像这样从 class 派生:
type
TInterfacedObjectWithoutReferenceCounting = class(TObject, IInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TInterfacedObjectWithoutReferenceCounting.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TInterfacedObjectWithoutReferenceCounting._AddRef: Integer;
begin
Result := -1;
end;
function TInterfacedObjectWithoutReferenceCounting._Release: Integer;
begin
Result := -1;
end;
但这也有其自身的风险。您必须确保在对象被销毁后您没有持有对该对象的任何引用。
unit example;
interface
type
ILettersSettings = interface
function Letters: String;
end;
INumbersSettings = interface
function Numbers: String;
end;
TSettings = class(TInterfacedObject, ILettersSettings, INumbersSettings)
private
fLoadedLetters: String;
fLoadedNumbers: String;
public
procedure LoadFromFile;
private {ILettersSettings}
function Letters: String;
private {INumbersSettings}
function Numbers: String;
end;
TNumbers = class
private
fNumbers: String;
public
constructor Create(settings: INumbersSettings);
end;
TLetters = class
private
fLetters: String;
public
constructor Create(settings: ILettersSettings);
end;
implementation
{ TSettings }
procedure TSettings.LoadFromFile;
begin
fLoadedLetters := 'abc';
fLoadedNumbers := '123';
end;
function TSettings.Letters: String;
begin
result := fLoadedLetters;
end;
function TSettings.Numbers: String;
begin
result := fLoadedNumbers;
end;
{ TNumbers }
constructor TNumbers.Create(settings: INumbersSettings);
begin
fNumbers := settings.Numbers;
end;
{ TLetters }
constructor TLetters.Create(settings: ILettersSettings);
begin
fLetters := settings.Letters;
end;
var
settings: TSettings;
letters: TLetters;
numbers: TNumbers;
begin
settings := TSettings.Create;
settings.LoadFromFile;
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
end.
我对整个项目的设置有异议。
settings := TSettings.Create;
settings.LoadFromFile;
我用这个对象创建了两个对象:数字和字母,通过构造函数注入它。
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
但我没有将它分配给构造函数中的任何变量,只是使用它。
{ TNumbers }
constructor TNumbers.Create(settings: INumbersSettings);
begin
fNumbers := settings.Numbers;
end;
{ TLetters }
constructor TLetters.Create(settings: ILettersSettings);
begin
fLetters := settings.Letters;
end;
所以在构造函数开始时引用计数=1,在构造函数结束时引用计数减少到0,对象被销毁。 所以符合:
numbers := TNumbers.Create(settings);
注入 nil 并引发运行时错误。
如何解决?
有很多方法可以解决这个问题...最简单的方法可能是让 TSettings 继承自 TComponent 而不是 TInterfacedObject。
TComponent默认实现了IInterface但不实现引用计数,所以当引用计数减少时,对象不会被销毁。这也意味着你必须自己销毁它。
TSettings = class(TComponent, ILettersSettings, INumbersSettings)
[...]
settings := TSettings.Create;
try
settings.LoadFromFile;
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
finally
Settings.Free;
end;
问题是您混合了两种不同的生命周期管理方法。您混合了引用计数生命周期管理和程序员控制的生命周期管理。
您的变量 settings
被声明为 TSettings
类型。尽管您没有显示该声明,但我们知道这是因为您可以调用 LoadFromFile
。这只有在 settings
被声明为 TSettings
.
因为 settings
是 class,这意味着您的代码对其生命周期负责。因此,当您分配给 settings
.
但是,当您调用 TLetters.Create
和 TNumbers.Create
时,您将接口引用分别传递给 ILetters
和 INumbers
。对于此代码,编译器确实发出引用计数代码。当您获得接口引用时,引用计数增加到 1,然后当该引用离开范围时减少到零。此时执行对象被销毁。
这一切的根本问题是你违反了生命周期管理规则。您不能像您所做的那样混合使用两种不同的方法。
人们通常采用的策略是要么始终使用程序员控制的管理,要么始终使用引用计数管理。这是你的选择。
如果您希望专门使用引用计数管理,那么您需要确保设置 class 的所有功能都可以通过接口使用。这意味着确保 LoadFromFile
可以通过接口调用。或者可能安排它由构造函数调用。
或者您可以切换到程序员控制的管理。在那种情况下,您不能从 TInterfacedObject
派生。您可能会像这样从 class 派生:
type
TInterfacedObjectWithoutReferenceCounting = class(TObject, IInterface)
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _AddRef: Integer; stdcall;
function _Release: Integer; stdcall;
end;
function TInterfacedObjectWithoutReferenceCounting.QueryInterface(const IID: TGUID;
out Obj): HResult;
begin
if GetInterface(IID, Obj) then begin
Result := S_OK;
end else begin
Result := E_NOINTERFACE;
end;
end;
function TInterfacedObjectWithoutReferenceCounting._AddRef: Integer;
begin
Result := -1;
end;
function TInterfacedObjectWithoutReferenceCounting._Release: Integer;
begin
Result := -1;
end;
但这也有其自身的风险。您必须确保在对象被销毁后您没有持有对该对象的任何引用。