2013-04-25 122 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