在两个pleaces中引用interfaced对象

时间:2017-02-14 15:09:29

标签: delphi interface reference-counting

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并且引发了运行时错误。

如何解决?

2 个答案:

答案 0 :(得分:4)

问题是你正在混合两种不同的生命周期管理方法。您可以参考计算生命周期管理和程序员控制的生命周期管理。

您的变量settings被声明为TSettings类型。虽然您没有显示该声明,但我们知道这是因为您可以致电LoadFromFile。只有在settings声明为TSettings类型时才有可能。

因为settings是一个类,这意味着您的代码负责其生命周期。因此,当您分配给settings时,编译器不会发出引用计数代码。

但是,当您致电TLetters.CreateTNumbers.Create时,您会将界面引用分别传递给ILettersINumbers。对于此代码,编译器会发出引用计数代码。获取接口引用时引用计数最多为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;