对于我正在研究的应用程序架构中的一个特定问题,接口似乎是一个很好的解决方案。具体来说,一些“业务对象”依赖于从实际应用程序中的数据库中提取的一堆设置。让这些业务对象请求接口(通过控制反转),让中心TDatabaseSettings
对象实现这些接口,可以更好地隔离,从而更容易进行单元测试。 / p>
然而,在Delphi中,接口似乎带有一个令人不愉快的奖励:引用计数。这意味着如果我做这样的事情:
type
IMySettings = interface
function getMySetting: String;
end;
TDatabaseSettings = class(..., IMySettings)
//...
end;
TMyBusinessObject = class(TInterfacedObject, IMySettings)
property Settings: IMySettings read FSettings write FSettings;
end;
var
DatabaseSettings: TDatabaseSettings;
// global object (normally placed in a controller somewhere)
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
在最后一行(O.Free
)上,我的全局DatabaseSettings
对象现在也被释放,因为它的最后一个接口引用(包含在O
中)丢失了! / p>
一种解决方案是使用接口存储“全局”DatabaseSettings
对象;另一种解决方案是覆盖TDatabaseSettings
类的引用计数机制,因此我可以继续将DatabaseSettings
作为普通对象进行管理(这与应用程序的其余部分更加一致)。 / p>
因此,总而言之,我的问题是:如何禁用特定类的接口引用计数机制?
我已经能够找到一些建议覆盖IInterface
方法_AddRef
和_Release
的类(示例中为TDatabaseSettings
)的信息;有没有人这样做过?
或者你会说我不应该这样做(令人困惑?只是一个坏主意?),并找到一个不同的解决方案来解决这个问题?
非常感谢!
答案 0 :(得分:13)
好的,你可以绕过它,但问题是你是否真的想要那样。 如果要使用接口,最好完全使用它们。因此,当您体验过它时,如果混合使用类和接口变量,就会遇到问题。
var
// DatabaseSettings: TDatabaseSettings;
DatabaseSettings : IMySettings;
//Now, in some function...
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
// ... do something with O
O.Free;
您现在有第二个对该界面的引用,并且丢失第一个将不会释放该对象。
同样可以保留类和对象:
var
DatabaseSettings: TDatabaseSettings;
DatabaseSettingsInt : IMySettings;
确保在创建对象后立即设置界面。
如果你真的想要禁用引用计数,你只需要创建一个实现IInterface的TObject的新后代。我在D2009测试了下面的例子,它可以工作:
// Query Interface can stay the same because it does not depend on reference counting.
function TMyInterfacedObject.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
if GetInterface(IID, Obj) then
Result := 0
else
Result := E_NOINTERFACE;
end;
constructor TMyInterfacedObject.Create;
begin
FRefCount := 1;
end;
procedure TMyInterfacedObject.FreeRef;
begin
if Self = nil then
Exit;
if InterlockedDecrement(FRefCount) = 0 then
Destroy;
end;
function TMyInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount);
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
Destroy;
end;
FreeRef只是像_Release那样降低引用计数。您可以在通常使用Free的地方使用它。
答案 1 :(得分:7)
_AddRef
,_Release
和_QueryInterface
是您要覆盖的内容。但是,您应该非常清楚自己正在做什么,因为这会导致内存泄漏或奇怪的,难以发现的错误。
不要从TInterfacedObject
下降,而是从TObject
下降,并实现返回1的前两个方法的自己版本。
答案 2 :(得分:7)
不要从TInterfacedObject下降,而是从标准TSingletonImplementation单元的System.Generics.Defaults下降。
答案 3 :(得分:4)
要禁用引用计数,AddRef和Release除了返回-1
之外什么都不做function TMyInterfacedObject._AddRef: Integer;
begin
Result := -1;
end;
function TMyInterfacedObject._Release: Integer;
begin
Result := -1;
end;
在没有引用计数的接口中有很多实用程序。如果使用引用计数,则不能混合对象和接口引用,因为会发生错误。通过禁用引用计数,您可以愉快地混合接口和对象引用,而不必担心您的对象突然被自动销毁。
答案 4 :(得分:3)
禁用此类问题的引用计数闻起来很糟糕。 更好的和架构解决方案是使用某种“单例”模式。 实现这一目标的最简单方法如下:
interface
type
TDatabaseSettings = class(..., IMySettings)
end;
function DatabaseSettings: IMySettings;
implementation
var
GDatabaseSettings: IMySettings;
function DatabaseSettings: IMySettings;
begin
if GDatabaseSettings = nil then GDatabaseSettings := TDatabaseSettings.Create;
Result := GDatabaseSettings;
end;
O := TMyBusinessObject.Create;
O.Settings := DatabaseSettings;
O.Free;
顺便说一下:当你使用接口时:总是使用接口变量!不要混合使用类en接口变量(使用“var Settings:IMySettings”而不是“var Settings:TDatabaseSettings”)。否则引用计数将阻塞(自动销毁,无效指针操作等)。 在上面的解决方案中,GDatabaseSettings也是“IMySettings”类型,因此它获得了正确的引用计数,并将持续到程序终止。
答案 5 :(得分:0)
或者只使用以下代码:
var I: IMyInterface; begin I := ...; ... Do whatever you want in a scope; Initialize(I); //- this will clear the interface variable without calling the _release. end.