【问题标题】:Code against an interface with TStrings and TStringList针对具有 TStrings 和 TStringList 的接口的代码
【发布时间】:2012-02-24 03:22:02
【问题描述】:

我在Why You Should Be Using Interfaces 上感兴趣地阅读了 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。

【问题讨论】:

  • 如果你想要我的意见:不要那样做!就像从不使用接口不是解决方案一样,对所有事情都使用接口也不是一个解决方案。甚至 Nick 在他的最新博文中也特别声称 TStrings/TStringList 可以完美地用作类实例。
  • TStrings 几乎是一个接口,它是一个抽象类,可以有不同的实现。只要有可能,我只传递 TStrings 作为参数的类型,而不是 TStringList。
  • 我同意@UweRaabe。接口很强大,而强大的工具经常被新手滥用。不要仅仅因为这是可能的,就使用接口引用而不是对象引用。我建议坚持接口的初衷——开放可扩展设计。
  • 开发人员是时髦的生物,不是吗。
  • 它会生成一个警告,不是吗,@Mjn?它是一个抽象类,或者至少它曾经是。 Delphi 仅在实例化抽象类时警告,而在大多数其他语言中它是一个错误。

标签: delphi interface delphi-xe2 tstringlist


【解决方案1】:

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 的实例来包装任何类型的 TStrings

像这样使用它:

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

您可能更愿意添加一个辅助函数来实际执行调用TIStrings.Create 的繁琐工作。

另请注意,生命周期可能是一个问题。您可能需要此包装器的变体,它不会接管对基础TStrings 实例的生命周期的管理。这可以通过 TIStrings 构造函数参数来安排。


我自己,我认为这是一个有趣的思想实验,但并不是一个真正明智的方法。 TStrings 类是一个抽象类,它几乎具有接口提供的所有好处。我认为按原样使用它没有真正的缺点。

【讨论】:

  • 为什么不直接创建一个 TIStrings.Strings: TStrings 默认属性来访问而不必包装所有方法?
  • @arnaud 这会使代码更加笨拙,因为您必须大量写出Strings 或存储到本地。默认属性只能避免数组属性的情况。但是,如果您想要的只是一个 RAII 模拟器,那我想这将是一个不错的选择
  • @Arnoaud +1 表示整洁和 MyList.Free 消失的事实。
  • @BrianFrost Arnaud 的建议无法编译。您只能拥有数组属性的默认属性。你必须在任何地方写MyList.Strings.Add
【解决方案2】:

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

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

    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 类实现它,当您在现有TStrings 后代上调用GetOleStrings 时会返回该类。当您有一个字符串列表并且您需要授予对另一个需要 IStringsIEnumString 接口的模块的访问权限时,请使用它。这些接口在其他情况下使用起来很笨拙——它们都没有提供TStrings 所做的所有事情——所以除非必须,否则不要使用它们。

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

【讨论】:

  • ISafeGuard 急需泛型和类型安全。写起来也很琐碎。如果您还没有使用 JCL,则可能不值得依赖 JCL 来解决这个问题。
  • @Rob:+1 一个有趣的解决方案。学术上令人着迷,但可能无法向我的同事解释!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 2014-12-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多