Delphi接口和类变量类型

3

我了解到接口是一种良好的方法来解耦代码。Nick Hodges在此方面写了一章节,阅读后我编写了以下代码:

//interfaces
type
 ILocalization = interface
  ['{1D144BCE-7D79-4672-8FB5-235422F712EE}']
  function localize(const aWordId: string): string;
 end;

type
 IActions = interface
  ['{31E8B24F-0B17-41BC-A9E4-F93A8E7F6ECF}']
  procedure addWord(const aIndex, aWord: string);
  procedure removeWord(const aIndex: string);
 end;

//implementation
type
 TLocalization = class sealed (TInterfacedObject, ILocalization, IActions)
  private
   FTranslationList: TDictionary<string, string>;
  public
   constructor Create;
   destructor Destroy; override;
   //interface implementations
   function localize(const aWordId: string): string;
   procedure addWord(const aIndex, aWord: string);
   procedure removeWord(const aIndex: string);
 end;

我计划使用这个类来本地化(翻译)我的Delphi安卓应用程序。


当我要创建一个该类的实例时,我会执行以下操作。

var
 Italiano: TLocalization;
begin
 Italiano := TLocalization.Create;
end;

既然 TLocalization 应该已经继承了 AddRef 和 Release 方法,所以我不会在 finally 中使用它们,但如果 Italiano 是一个类类型而不是接口类型,这是否仍然发生?

我的意思是:

  • Italiano: TLocalization -> 在这里我可以使用所有的方法
  • Italiano: ILocalization -> 在这里我只能使用 localize 函数

考虑到(如我之前所说),TLocalization 继承 AddRef 和 Release 方法,如果我理解正确,我就不需要释放它。尽管如此,对于 ILocalization,它还有其他好处吗?我不明白以上两种情况的区别是什么。

1个回答

5
var
  Italiano: TLocalization;
begin
  Italiano := TLocalization.Create;
  // do stuff
end;

这将使您陷入混合不同生命周期模型的风险中。目前代码中,AddRef未被调用,因此引用计数为0。因此,您将泄漏此对象。

所以您可能需要更改代码:

var
  Italiano: TLocalization;
begin
  Italiano := TLocalization.Create;
  try
    // do stuff
  finally
    Italiano.Free;
  end;
end;

现在你没有泄漏了。
太好了。但是如果您确实引用了呢?
var
  Italiano: TLocalization;
  Localization: ILocalization;
begin
  Italiano := TLocalization.Create;
  try
    Localization := Italiano as ILocalization;
    // do stuff
  finally
    Italiano.Free;
  end;
end;

现在当你给 Localization 赋值时,会调用 AddRef。这样引用计数就变成了 1。当 Localization 超出作用域,会调用 Release,引用计数就回归到 0,并销毁实例。不幸的是,你也显式地销毁了它。对象需要被准确地销毁一次。
最干净、最简单的方法是:不要混合使用不同的生命周期模型。如果你用引用计数管理生命周期,就只用这个方法。确保在创建对象时总是获取一个引用,并让编译器生成引用计数代码来管理生命周期。
确保遵循此规则的一种方法是通过接口引用访问对象。像这样:
var
  Italiano: ILocalization;
begin
  Italiano := TLocalization.Create;
  // do stuff
end;

好的,我想现在我明白了。那么让我们看看你写的最后一段代码。如果addWord、removeWord和localize这三个方法都在ILocalization内部,我就可以完全使用我的类。但是目前我不能这样做,因为ILocalization仅对localize方法有refCount,而不是其他两个方法,所以我无法调用另外两个方法。我说得对吗? - Raffaele Rossi
1
不,对象实现了这两个接口,并且对象拥有引用计数,而不是接口。因此,如果您想要访问其他方法,请使用 var Actions: IActions; .... Actions := Italiano as IActions;。现在您有了两个接口变量,它们背后是同一个单一对象,该对象现在的引用计数为2。 - David Heffernan
好的,我可以在“正常方式”下使用类,使用try finally,或者我必须进行类型转换才能安全地使用IActions或ILocalizations。 - Raffaele Rossi
2
如果您以正常方式使用类,则无法获取接口引用,因为这会导致泄漏。这就是上面第三个摘录的全部意义。但是,为什么您需要这样做呢?简单的规则是,一旦您开始通过引用计数来管理生命周期,就要完全按照这种方式进行。不要混合生命周期模型。 - David Heffernan
我曾认为接口的唯一用处是可以通过创建松耦合代码来扩展类的功能。但从你所说的内容中,我误解了如何实现它们。现在我明白了为什么混合模型是错误的。 - Raffaele Rossi
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接