[转]接口小论

  完成一个功能时用到观察者模式,将业务类的变化通知给外部的窗体,同时也通知给另一个类,假设为TTest,这个类继承自TInterfaceObject。窗体和TTest都实现了一个接口,因此业务类通过接口可以将变化通知给外部。 

  感觉这个类层次应用得很好,但问题出现了,业务类用IInterfaceList管理这些接口,程序关闭时,IInterfaceList将所有的接口置为Nil,按以前的理解,实现接口的类对象会同时被消毁。即TTest对象和窗体类对象自动会被消毁。但事实上并不是这样。

  经过一番研究,才明白个中原因,有结论如下:

1.类如果从TInterfaceObject继承下来,并且实现某个接口。

该接口变量可以自动释放实现该接口的类,例子如下:

type

ITest = interface(IInterface)
  procedure test;
end;

TTest = class(TInterfacedObject, ITest)
public
  procedure test;
  destructor Destroy; override;
end;

{TTest}
procedure TTest.test;
begin
  showmessage('ok');
end;

destructor Destroy; 
begin
  showmessage('ok');
  inherited;
end;

//测试
var
  Test: ITest;
begin
  Test := TTest.Create;
  try
    Test.test;
  finally
    Test := nil;
  end;
end;

执行后,弹出两次对话框,说明TTest对象最后被释放了,原因是Test := nil 后,最终于会调用TInterfaceObject的_Release方法,在里面如果引用计数为0,则调用Destroy释放自己。

2.  类如果从TComponent继承下来,并且实现某个接口。 

该接口变量不能自动释放实现该接口的类,把上面的TTest改为从TComponent继承,测试代码如下:

var
  Test: ITest;
begin
  Test := TTest.Create(nil);
  try
    Test.test;
  finally
    Test := nil;
  end;
end;

 

  这时只弹出一个对话框,说明对象没有被释放,原因是Test := nil后,最终于会调用TComponent的_Release,而里面如果引用计数为0并不会释放自己。 

3.要对象可以由接口自动释放,必须该类自己实现IInterface,并在_Release调用Destroy。例子如下:

type
  ITest = interface(IInterface)
    procedure test;
  end;

  TTest = class(TComponent,  IInterface, ITest)
  public
    { IInterface }
    function QueryInterface(const IID: TGUID;
      out Obj): HResult; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    { ITest }
    procedure test;
    destructor Destroy; override;
  end;
...
{ TTest }

destructor TTest.Destroy;
begin
  ShowMessage('ok');
  inherited;
end;

function TTest.QueryInterface(const IID: TGUID;
  out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

procedure TTest.test;
begin
  ShowMessage('ok');
end;

function TTest._AddRef: Integer;
begin
  Result := InterlockedIncrement(FRefCount);
end;

function TTest._Release: Integer;
begin
  Result := InterlockedDecrement(FRefCount);
  if Result = 0 then
    Destroy;
end;

//测试代码
var
  Test: ITest;
begin
  Test := TTest.Create;
  try
    Test.test;
  finally
    Test := nil;
  end;
end;

运行结果,对话框弹了两次,对象可以由接口释放了。

Delphi的接口真是太不直观了。

原文地址:https://www.cnblogs.com/h2zZhou/p/9208303.html