使用TStrings和TStringList针对接口编写代码

7
我很感兴趣地阅读了Nick Hodges关于为什么你应该使用接口的博客文章,因为我已经喜欢在我的编码中使用接口,所以我决定看看如何将其扩展到相当低的层面,并调查VCL类中对此的支持。
我需要的一个常见结构是对TStringList执行一些简单操作,例如使用以下代码将小型文本文件列表加载到逗号文本字符串中:
var
  MyList : TStrings;
  sCommaText : string;
begin
  MyList := TStringList.Create;
  try
    MyList.LoadFromFile( 'c:\temp\somefile.txt' );
    sCommaText := MyList.CommaText;

    // ... do something with sCommaText.....

  finally
    MyList.Free;
  end;
end;

如果我可以使用MyList作为接口来编写代码,这似乎是一个不错的简化 - 它将消除try-finally并提高可读性:

var
  MyList : IStrings;
         //^^^^^^^
  sCommaText : string;
begin
  MyList := TStringList.Create;
  MyList.LoadFromFile( 'c:\temp\somefile.txt' );
  sCommaText := MyList.CommaText;

  // ... do something with sCommaText.....

end;

我看不到IStrings的定义,至少在Classes.pas中没有,虽然在线上与OLE编程有关时提到了它。它是否存在?这是有效简化吗?我正在使用Delphi XE2。


4
如果你想听我的建议:不要那样做!就像从不使用接口不是解决方案一样,对于所有事情都使用接口也不是一个解决方案。甚至在尼克最新的博客文章中,他明确声称TStrings / TStringList可以完美地用作类实例。 - Uwe Raabe
4
TStrings几乎是一个接口,它是一个抽象类,可以有不同的实现方式。尽可能地,我只将TStrings作为参数类型传递,而不是TStringList。 - mjn
我同意@UweRaabe的观点。接口是强大的工具,而新手经常会误用强大的工具。不要仅仅因为可以这样做就使用接口引用代替对象引用。我建议遵循接口的原始目的——开放可扩展的设计。 - kludg
开发人员是时髦的生物,不是吗。 - Warren P
1
它生成一个警告,不是吗,@Mjn?它是一个抽象类,或者至少曾经是。在Delphi中,当实例化抽象类时只会警告,而在大多数其他语言中则是错误。 - Rob Kennedy
显示剩余5条评论
2个回答

6

RTL/VCL中没有与您想要的相同接口(暴露与TStrings相同的接口)的界面。如果您想使用这样的东西,您需要自己发明它。

您可以使用以下包装器来实现:

type
  IStrings = interface
    function Add(const S: string): Integer;
  end;

  TIStrings = class(TInterfacedObject, IStrings)
  private
    FStrings: TStrings;
  public
    constructor Create(Strings: TStrings);
    destructor Destroy; override;
    function Add(const S: string): Integer;
  end;

constructor TIStrings.Create(Strings: TStrings);
begin
  inherited Create;
  FStrings := Strings;
end;

destructor TIStrings.Destroy;
begin
  FStrings.Free; // don't use FreeAndNil because Nick might see this code ;-)
  inherited;
end;

function TIStrings.Add(const S: string): Integer;
begin
  Result := FStrings.Add(S);
end;

自然地,您会将TStrings接口的其余部分封装在一个真正的类中。使用像这样的包装器类来完成,以便您可以通过访问其实例来包装任何类型的TStrings

像这样使用它:

var
  MyList : IStrings;
....
MyList := TIStrings.Create(TStringList.Create);

您可能希望添加一个辅助函数来实际执行调用 TIStrings.Create 的繁琐工作。
请注意,生命周期可能是一个问题。您可能需要一种不接管底层 TStrings 实例的管理生命周期的包装器变体。可以通过 TIStrings 构造函数参数进行安排。
我认为这是一个有趣的思想实验,但并不是一个明智的方法。 TStrings 类是一个抽象类,几乎拥有接口提供的所有好处。我认为使用它没有真正的缺点。

4
为什么不直接创建一个 TIStrings.Strings: TStrings 默认属性,以便无需包装所有方法即可访问? - Arnaud Bouchez
1
@arnaud这会使代码变得更加臃肿,因为您将不得不经常编写Strings或存储到本地。默认属性仅适用于数组属性,但如果您只想要一个RAII仿真器,那么这就是一个好方法。 - David Heffernan
@Arnoaud +1 因为整洁,而且 MyList.Free 消失了。 - Brian Frost
@BrianFrost,Arnaud的建议无法编译。你只能为数组属性设置默认属性。你需要在每个地方都写MyList.Strings.Add - David Heffernan

4

由于TStrings是一个抽象类,它的接口版本并没有太多价值。任何实现该接口的人都肯定是TStrings的后代,因为没有人想重新实现TStrings做的所有事情。我认为需要TStrings接口有两个原因:

  1. Automatic resource cleanup. You don't need a TStrings-specific interface for that. Instead, use the ISafeGuard interface from the JCL. Here's an example:

    var
      G: ISafeGuard;
      MyList: TStrings;
      sCommaText: string;
    begin
      MyList := TStrings(Guard(TStringList.Create, G));
    
      MyList.LoadFromFile('c:\temp\somefile.txt');
      sCommaText := MyList.CommaText;
    
      // ... do something with sCommaText.....
    end;
    

    To protect multiple objects that should have the same lifetime, use IMultiSafeGuard.

  2. Interoperation with external modules. This is what IStrings is for. Delphi implements it with the TStringsAdapter class, which is returned when you call GetOleStrings on an existing TStrings descendant. Use that when you have a string list and you need to grant access to another module that expects IStrings or IEnumString interfaces. Those interfaces are clunky to use otherwise — neither provides all the things TStrings does — so don't use them unless you have to.

    If the external module you're working with is something that you can guarantee will always be compiled with the same Delphi version that your module is compiled with, then you should use run-time packages and pass TStrings descendants directly. The shared package allows both modules to use the same definition of the class, and memory management is greatly simplified.


ISafeGuard 对泛型和类型安全有很高的需求。而且它也很容易编写。如果您还没有使用 JCL,也许没必要仅为了使用它而依赖于 JCL。 - David Heffernan
@Rob:这是一个有趣的解决方案,值得一加。学术上很迷人,但可能无法向我的同事解释清楚! - Brian Frost

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