【问题标题】:Delphi interface implementsDelphi接口实现
【发布时间】:2013-04-19 03:01:12
【问题描述】:

我希望引用计数应该在接口实现中的外部聚合对象上起作用。 如果我可以参考另一个例子:Clarity in classes implementing multiple interfaces (alternative to delegation):

这是行为的最小再现:

program SO16210993;

{$APPTYPE CONSOLE}

type
  IFoo = interface
    procedure Foo;
  end;

  TFooImpl = class(TInterfacedObject, IFoo)
    procedure Foo;
  end;

  TContainer = class(TInterfacedObject, IFoo)
  private
    FFoo: IFoo;
  public
    constructor Create;
    destructor Destroy; override;
    property Foo: IFoo read FFoo implements IFoo;
  end;

procedure TFooImpl.Foo;
begin
  Writeln('TFooImpl.Foo called');
end;

constructor TContainer.Create;
begin
  inherited;
  FFoo := TFooImpl.Create;
end;

destructor TContainer.Destroy;
begin
  Writeln('TContainer.Destroy called');//this line never runs
  inherited;
end;

procedure Main;
var
  Foo : IFoo;
begin
  Foo := TContainer.Create;
  Foo.Foo;
end;

begin
  Main;
  Readln;
end.

如果我不使用implements,而是在TImplementor 类中实现接口,然后析构函数运行。

【问题讨论】:

  • “我错过了什么吗?”我不知道。但我们当然是。你忘了包含代码!需要演示行为的完整程序。否则我们必须猜测。
  • 你有一些额外的参考或参考循环。为 TFirstSecond._AddRef 和 TFirstSecond._Release 添加覆盖并在此处放置断点,获取完整的引用列表并查看哪些未清除
  • 嗯,问题是你的接口是委托的。不知道为什么会导致这种行为。
  • 好问题。我冒昧地制作了一个非常简单的演示测试用例。
  • 委托的想法是 Delphi 中一个不错的官方功能,但它似乎不起作用?

标签: delphi interface delphi-2007 implements


【解决方案1】:

这里发生的是您调用TContainer.Create 并为对象创建一个实例。但是您随后将该实例分配给接口引用,即全局变量Foo。因为该变量的类型为IFoo,接口委托意味着实现对象是TFooImpl 的实例,而不是TContainer 的实例。

因此没有任何东西引用TContainer 的实例,它的引用计数永远不会增加,因此它永远不会被销毁。

我不认为有一个非常简单的方法可以解决这个问题。您也许可以使用TAggregatedObject,但它可能无法解决您的问题。这将迫使您将 TContainer.FFoo 声明为 TFooImpl 类型,我想您不想这样做。无论如何,这就是以这种方式重新投射的样子:

program SO16210993_TAggregatedObject;

{$APPTYPE CONSOLE}

type
  IFoo = interface
    procedure Foo;
  end;

  TFooImpl = class(TAggregatedObject, IFoo)
    procedure Foo;
  end;

  TContainer = class(TInterfacedObject, IFoo)
  private
    FFoo: TFooImpl;
    function GetFoo: IFoo;
  public
    destructor Destroy; override;
    property Foo: IFoo read GetFoo implements IFoo;
  end;

procedure TFooImpl.Foo;
begin
  Writeln('TFooImpl.Foo called');
end;

destructor TContainer.Destroy;
begin
  Writeln('TContainer.Destroy called');//this line does run
  FFoo.Free;
  inherited;
end;

function TContainer.GetFoo: IFoo;
begin
  if not Assigned(FFoo) then
    FFoo := TFooImpl.Create(Self);
  Result := FFoo;
end;

procedure Main;
var
  Foo : IFoo;
begin
  Foo := TContainer.Create;
  Foo.Foo;
end;

begin
  Main;
  Readln;
end.

documentation 确实谈到了这一点:

用于实现委托接口的类应该派生自 TAggregationObject。

最初我找不到这个TAggregationObject 的任何文档。最后我意识到它实际上被命名为TAggregatedObject 并且是documented

TAggregatedObject 提供了一个内部对象的功能 通过实现 IInterface 方法来聚合以委托给 控制 IInterface。

聚合对象是由多个接口组成的对象 对象。每个对象都实现了自己的行为和接口,但是 所有对象共享相同的引用计数,即 控制器对象。在容器模式中,控制器是 容器对象。

TAggregatedObject 本身不支持任何接口。然而,作为 是典型的聚合,它确实实现了 IInterface,由从它派生的对象使用。 因此,TAggregatedObject 作为类的基础 实现用于创建对象的接口,这些对象是 聚合。

TAggregatedObject 用作​​创建包含的类的基础 对象和连接对象。使用 TAggregatedObject 可确保 调用 IInterface 方法委托给控制 IInterface 的总和。

控制 IInterface 在构造函数中指定 TAggregatedObject 并由 Controller 属性指示。

另外还有来自源代码cmets的这个:

TAggregatedObject 和 TContainedObject 是合适的基类 对于旨在聚合或包含在 外部控制对象。在使用“实现”语法时 外部对象类声明中的接口属性,使用这些 实现内部对象的类型。

由聚合对象代表实现的接口 控制器不应与其他接口区分开来 由控制器提供。聚合对象不得维护 他们自己的引用计数 - 他们必须有相同的生命周期 他们的控制器。为了实现这一点,聚合对象反映了 控制器的引用计数方法。

TAggregatedObject 只是将 QueryInterface 调用反映到它的 控制器。从这样一个聚合对象中,可以得到任何 控制器支持的接口,并且只有控制器支持的接口 控制器支持。这对于实现控制器很有用 使用一个或多个内部对象来实现的类 在控制器类上声明的接口。聚合促进 跨对象层次结构的实现共享。

TAggregatedObject 是大多数聚合对象应该继承的 from,尤其是与“实现”一起使用时 语法。

【讨论】:

  • @FabricioAraujo 我最后找到了文档。有错别字!!
  • 我被我在别处找到的无数例子误导了。引用计数必须发生在某个地方是完全有道理的。感谢您的回答
  • 不客气。这对我来说是一次有趣的学习经历!
  • Marcos Santos 提出了一个在我看来是优雅的解决方案,允许接口实现既可以用作常规对象,也可以用作聚合对象link。解决方案是从 TInterfacedObject 继承实现类,因此只要将相同的实现与关键字 implements 一起使用,它就可以用作独立对象和从 TAggregatedObject 下降的装饰器。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-29
  • 1970-01-01
  • 1970-01-01
  • 2011-01-13
  • 1970-01-01
  • 2021-05-26
相关资源
最近更新 更多