Interface inheritance without generics
Asked Answered
A

1

5

I am trying to implement an interface to convert records in a dataset to Delphi records in a pre-generics version of Delphi. I don't like the interface at the moment, as it will always need calls to Supports which I'd like to avoid if possible and was wondering if there's a better way of doing it that I'm missing.

So far I have an navigation interface and data retrieval interface defined:

IBaseRecordCollection = interface
  procedure First;
  procedure Next;
  function BOF: boolean;
  ... // other dataset nav stuff
end;

IRecARecordCollection = interface
  function GetRec: TRecA;
end;

IRecBRecordCollection = interface
  function GetRec: TRecB;
end;

Basically I have a concrete base class that contains a private dataset and implements IBaseRecordCollection and concrete class for each RecordCollection interface which derives from an abstract class implementing the IBaseRecordCollection (handled by an implements property) with the implementation of the record retrieval routine:

TAbstractTypedRecordCollection = class(TInterfacedObject, IBaseRecordCollection)
private
  FCollection: IBaseRecordCollection;
protected
  property Collection: IBaseRecordCollection read FCollection implements IBaseRecordCollection;
public
  constructor Create(aRecordCollection: IBaseRecordCollection);
end;

TRec1RecordCollection = class(TAbstractTypedRecordCollection, IRecARecordCollection);
public
  function GetRec: TRecA;
end;

Now, to use this I'm forced to have a builder that returns a IRecARecordCollection and then mess around with Supports, which I'm not keen on as it will always be used in this fashion.

i.e.

procedure GetMyRecASet;
var
  lRecARecordCollection: IRecARecordCollection;
  lRecordCollection: IBaseRecordCollection;
begin
  lRecARecordCollection := BuildRecACollection;
  if not supports(lRecARecordCollection, IBaseRecordCollection, lRecordCollection) then 
     raise exception.create();
  while not lRecordCollection.EOF do
  begin
    lRecARecordCollection.GetRec.DoStuff;
    lRecordCollection.Next;
  end;
end;

Although this works, I'm not keen on the supports call and mixing my lRecordCollections and my lRecARecordCollections like this. I had originally hoped to be able to do something like:

IBaseRecordCollection = interface
  // DBNav stuff
end;

IRecARecordCollection = interface (IBaseRecordCollection)
  function GetRec: TRecA;
end;

TRec1RecordCollection = class(TInterfacedObject, IRecARecordCollection)
private
  FCollection: IBaseRecordCollection;
protected
  property Collection: IBaseRecordCollection read FCollection implements IBaseRecordCollection;
public
  function GetRec: TRecA;
end;

but unfortunately Delphi wasn't smart enough to realise that the implementation of IRecARecordCollection was split over the base IBaseRecordCollection in the Collection property implements call and the TRec1RecordCollection object.

Are there any other suggestions for neater ways to acheive this?

-- edit to give a (longer) reply to @David's answer than possible in a comment

The suggested solution of:

IBaseRecordCollection = interface ['{C910BD0A-26F4-4682-BC82-605C4C8F9173}']
  function GetRecNo: integer;
  function GetRecCount: integer;
  function GetFieldList: TFieldList;
  function EOF: boolean;
  function BOF: boolean;
  ...
end;

IRec1RecordCollection = interface (IBaseRecordCollection) ['{E12F9F6D-6D57-4C7D-AB87-8DD50D35DCA2}']
  function GetRec: TRec1;
  property Rec: TRec1 read GetRec;
end;

TAbstractTypedRecordCollection = class(TInterfacedObject, IBaseRecordCollection)
private
  FCollection: IBaseRecordCollection;
protected
  property Collection: IBaseRecordCollection read FCollection implements IBaseRecordCollection;
public
  constructor Create(aRecordCollection: IBaseRecordCollection);
end;

TRec1RecordCollection = class(TAbstractTypedRecordCollection, IRec1RecordCollection, IBaseRecordCollection)
private
  function GetRec: TRec1;
public
  property Rec: TRec1 read GetRec;
end;

isn't compiling. It's complaining that TRec1RecordCollection cannot find methods related to IBaseRecordCollection. I also tried moving the Collection property from Abstract to Rec1RecordCollection and redeclaring the property in TRec1RecordCollection all with the same result

Looking a bit deeper it appears that direct inheritance of a class implementing IBaseRecordCollection would work but Delphi can't handle doing it indirectly via a property using implements.

Aristate answered 27/9, 2013 at 16:17 Comment(1)
For completeness, as there appears to be a bug in the D2006 compiler (A bug????) that means that @David's solution below doesn't work I have had to chop out the dependency injection and just have all TRecXRecordCollections inherit from the concrete TBaseRecordCollection rather than TAbstractTypedRecordCollectionAristate
S
7

Your code is almost there. The implements directive in your code fails to compile because you only declared that your class implements the derived interface. As it stands, your class does not implement the interface that the implements directive refers to, namely IBaseRecordCollection. You might think that would be inferred from the inheritance but it is not.

To solve your problem you simply need to declare that TRec1RecordCollection implements both interfaces:

type
  TRec1RecordCollection = class(TInterfacedObject, IBaseRecordCollection, 
    IRecARecordCollection)
  ....
  end;

Make just the one small change and your code will compile.

Update

Your edit to the question changes this somewhat. The code in my answer does indeed compile, given the code in your original question. However, add any method into IBaseRecordCollection and the compile will not accept it.

The compiler should accept this code and the fact that it does not is because of a compiler bug. Modern versions of Delphi will accept the code in your update to the question.

Unless you upgrade your compiler you will not be able to make your intended design work.

Sancho answered 27/9, 2013 at 16:20 Comment(7)
Yes, but then I have to pass around the concrete implementation rather than the interface, surely? Doesn't this negate what we're trying to acheive with interfaces? Applying this with the two interfaces would still result in the supports code above where I get one interface from the builder and have to use Supports to get the other and it is this that I'm trying to avoid.Aristate
No, pass around IRecARecordCollectionSancho
Brilliant - I'll give it a go when I'm back in the office on Monday and accept if it worksAristate
See edit to question above - short version: it didn't compileAristate
Right, now you are asking a different question. Given the code in the original question, the code in my answer compiles.Sancho
OK, it seems that there's a compiler bug. I do want to stress however, that the code in my answer does compile. It compiles even in old versions like Delphi 6. The code in your edited question is a little different though. And the code in your edit compiles in modern Delphi. It's time you gave up on the old Delphi that you are using.Sancho
I wish that was in my power to do so :-(, unfortunately I'm not the one holding the purse strings. I think I'm stuck with either implementing the interface in the abstract class and passing everything on to FCollection or putting the concrete implementation of IBaseRecordCollection into TAbstractTypedRecordCollectionAristate

© 2022 - 2024 — McMap. All rights reserved.