针对带有 TStrings 和 TStringList 的接口进行编码

发布于 2025-01-01 13:22:16 字数 997 浏览 3 评论 0原文

我饶有兴趣地阅读了 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 编程相关的引用。它存在吗?这是有效的简化吗?我使用的是德尔福XE2。

I read with interest Nick Hodges blog on Why You Should Be Using Interfaces
and since I'm already in love with interfaces at a higher level in my coding I decided to look at how I could extend this to quite low levels and to investigate what support for this existed in the VCL classes.

A common construct that I need is to do something simple with a TStringList, for example this code to load a small text file list into a comma text string:

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;

It would seem a nice simplification if I could write with using MyList as an interface - it would get rid of the try-finally and improve readability:

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

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

end;

I can't see an IStrings defined though - certainly not in Classes.pas, although there are references to it in connection with OLE programming online. Does it exist? Is this a valid simplification? I'm using Delphi XE2.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你的心境我的脸 2025-01-08 13:22:16

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 类是一个抽象类,它几乎具有接口提供的所有优点。我认为按原样使用它没有真正的缺点。

There is no interface in the RTL/VCL that does what you want (expose the same interface as TStrings). If you wanted to use such a thing you would need to invent it yourself.

You would implement it with a wrapper like this:

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;

Naturally you would wrap up the rest of the TStrings interface in a real class. Do it with a wrapper class like this so that you can wrap any type of TStrings just by having access to an instance of it.

Use it like this:

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

You may prefer to add a helper function to actually do the dirty work of calling TIStrings.Create.

Note also that lifetime could be an issue. You may want a variant of this wrapper that does not take over management of the lifetime of the underlying TStrings instance. That could be arranged with a TIStrings constructor parameter.


Myself, I think this to be an interesting thought experiment but not really a sensible approach to take. The TStrings class is an abstract class which has pretty much all the benefits that interfaces offer. I see no real downsides to using it as is.

幻梦 2025-01-08 13:22:16

由于 TStrings 是一个抽象类,因此它的接口版本不会提供太多内容。无论如何,该接口的任何实现者肯定都是 TStrings 后代,因为没有人愿意重新实现 TStrings 所做的所有事情。我认为需要 TStrings 接口有两个原因:

  1. 自动资源清理。为此,您不需要特定于 TStrings 的接口。相反,请使用 JCL 中的 ISafeGuard 界面。这是一个例子:

    <前><代码>变量
    G:ISafeGuard;
    我的列表:TStrings;
    sCommaText:字符串;
    开始
    MyList := TStrings(Guard(TStringList.Create, G));

    MyList.LoadFromFile('c:\temp\somefile.txt');
    sCommaText := MyList.CommaText;

    // ...用 sCommaText 做一些事情......
    结尾;

    要保护应具有相同生命周期的多个对象,请使用IMultiSafeGuard

  2. 与外部模块的互操作。这就是IStrings的用途。 Delphi 使用 TStringsAdapter 类来实现它,当您在现有 TStrings 后代上调用 GetOleStrings 时,会返回该类。当您有字符串列表并且需要授予对需要 IStringsIEnumString 接口的另一个模块的访问权限时,请使用它。否则,这些接口使用起来很笨拙——它们都不提供 TStrings 所做的所有事情——所以除非必要,否则不要使用它们。

    如果您正在使用的外部模块保证将始终使用与您的模块编译时相同的Delphi版本进行编译,那么您应该使用运行时包并且直接传递 TStrings 后代。共享包允许两个模块使用相同的类定义,并且内存管理大大简化。

Since TStrings is an abstract class, an interface version of it wouldn't provide much. Any implementer of that interface would surely be a TStrings descendant anyway, because nobody would want to re-implement all the things TStrings does. I see two reasons for wanting a TStrings interface:

  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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文