Reputation: 13454
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.
Upvotes: 7
Views: 3480
Reputation: 163247
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:
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
.
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.
Upvotes: 4
Reputation: 612794
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.
Upvotes: 6