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 编程中有对其的引用。它存在吗?这是有效的简化吗?我正在使用德尔福 XE2。

4

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

于 2012-02-01T12:43:20.620 回答
4

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

  1. 自动资源清理。你不需要一个TStrings特定的接口。相反,请使用ISafeGuardJCL 中的接口。这是一个例子:

    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;
    

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

  2. 与外部模块的互操作。这是IStrings为了什么。Delphi 使用TStringsAdapter类实现它,当您调用GetOleStrings现有TStrings后代时会返回该类。当您有一个字符串列表并且您需要授予对期望IStringsIEnumString接口的另一个模块的访问权限时使用它。这些接口在其他情况下使用起来很笨拙——它们都没有提供所有的东西TStrings——所以除非你必须,否则不要使用它们。

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

于 2012-02-01T14:35:42.037 回答