Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code against an interface with TStrings and TStringList

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.

like image 784
Brian Frost Avatar asked Feb 01 '12 12:02

Brian Frost


1 Answers

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.

like image 129
David Heffernan Avatar answered Sep 23 '22 01:09

David Heffernan