2013-04-25 89 views
13

我希望引用计数应该在接口实现中的外部聚合对象上工作。 如果我可以参考另外一个例子: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类的接口,那么析构函数运行。

+5

“我错过了什么吗?”我不知道。但我们当然是。你忘了包含代码!表明行为的完整程序是必需的。否则,我们必须猜测。 – 2013-04-25 09:24:34

+1

你有一些额外的引用或引用循环。为TFirstSecond._AddRef和TFirstSecond._Release添加重写并在其中放置断点,获取完整的引用列表并查看哪些未被清除 – 2013-04-25 10:43:03

+0

问题是,您的接口已委派。不知道为什么会导致这种行为。 – 2013-04-25 13:37:46

回答

15

这里发生的事情是您拨打TContainer.Create并为对象创建实例。但是,您随后将该实例分配给接口引用,即全局变量Foo。由于该变量的类型为IFoo,因此接口委派意味着实施对象是TFooImpl而非实例TContainer的实例。

因此,任何事情都不会引用TContainer的实例,其引用计数永远不会增加,因此它永远不会被销毁。

我不认为有一个非常简单的解决方法。您可能可以使用TAggregatedObject,但它可能无法解决您的问题。这会迫使你声明TContainer.FFooTFooImpl,我想你不想这样做。总之,这里是什么样子再投这种方式:

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属性指示。

另外有此从所述源代码中的注释:

TAggregatedObject和TContainedObject是对于打算用在一个 外控制对象要被汇集或包含接口对象合适的基类 。在外部对象类声明的接口属性中使用“实现”语法时,请使用这些 类型来实现内部对象。

由代表 控制器的聚合对象实现的接口不能与控制器提供的其他接口 区分开。聚合对象不得维护自己的引用计数 - 它们的控制器必须具有与其相同的生命周期 。为了达到这个目的,聚合对象向控制器反映了参考计数方法 。

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

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

+0

@FabricioAraujo我在最后找到了文档。有一个错字! – 2013-04-25 20:07:42

+0

我被其他地方发现的无数例子误导了。引用计数必须发生在某个地方是非常合理的。感谢您的回答 – Gryffe 2013-04-26 07:32:22

+0

不客气。对我来说这是一个有趣的学习经历! – 2013-04-26 07:34:19