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;
所以在构造函数的开头有引用count = 1,并且在构造函数引用结束时,引用计数减少到0,并且对象被销毁。 所以一致:
numbers := TNumbers.Create(settings);
注入了nil并且引发了运行时错误。
如何解决?
答案 0 :(得分:4)
问题是你正在混合两种不同的生命周期管理方法。您可以参考计算生命周期管理和程序员控制的生命周期管理。
您的变量settings
被声明为TSettings
类型。虽然您没有显示该声明,但我们知道这是因为您可以致电LoadFromFile
。只有在settings
声明为TSettings
类型时才有可能。
因为settings
是一个类,这意味着您的代码负责其生命周期。因此,当您分配给settings
时,编译器不会发出引用计数代码。
但是,当您致电TLetters.Create
和TNumbers.Create
时,您会将界面引用分别传递给ILetters
和INumbers
。对于此代码,编译器会发出引用计数代码。获取接口引用时引用计数最多为1,当该引用离开作用域时,引用计数最多为0。此时实施对象将被销毁。
所有这一切的根本问题在于你违反了终身管理规则。你不能像过去那样混合使用两种不同的方法。
人们采用的通常政策是始终使用程序员控制的管理,或始终引用计数管理。这是你的选择。
如果您希望仅使用引用计数管理,则需要确保通过接口提供设置类的所有功能。这意味着确保可以通过接口调用LoadFromFile
。或者也许安排构造函数调用它。
或者,您可以切换到程序员控制的管理。在这种情况下,您不得从TInterfacedObject
派生。你可能会从这样的类派生出来:
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;
但这带来了风险。在对象被销毁后,您必须确保没有对该对象的任何引用。
答案 1 :(得分:0)
有很多方法可以解决这个问题......最简单的方法可能是让TSettings继承TComponent而不是TInterfacedObject。
TComponent实现了IInterface,但默认情况下没有实现引用计数,所以当refcount递减时,对象不会被销毁。这也意味着你必须自己摧毁它。
TSettings = class(TComponent, ILettersSettings, INumbersSettings)
[...]
settings := TSettings.Create;
try
settings.LoadFromFile;
letters := TLetters.Create(settings);
numbers := TNumbers.Create(settings);
finally
Settings.Free;
end;