1
\$\begingroup\$

I'm working on wrapping the last.fm web API. I'm trying to use Interfaces to be able to easily make calls via functions which return an interface (as opposed to an object which would need to be free'd). I believe I have the fundamentals down, but I'm afraid I may be doing something wrong, and need to know the correct way to go about this before I go much further.

What I'm creating is first of all two separate units: LastFm.pas (main implementation) as well as LastFmIntf.pas (interface definitions). The concept I'm trying to get down is wrapping a list of interfaces within an interface its self. Meaning, two interfaces, one contains a list of search results, and the other is each search result its self. In this example, I'm wrapping the album.search call, returning an interfaced list of interfaced objects representing each album in the results. The results are read-only, I don't intend to let the properties be modified. The actual data is populated by adding interfaced objects and feeding the values into them. After those values are added, I have no further reason to keep a reference to the original object - only the interface that's returned in the function.

From what I understand, while using interfaces, because of reference counting, when all references to an interface are gone, the interfaced object will free its self - which is the fundamental reasoning behind this design. I also to intend to later wrap this further into a DLL, but that's a different topic. But that's one more reason I'm using interfaces and defining them in a separate unit.

To reiterate, the following IAlbumResults interface will be returned from a function TAlbum.Search, which populates the list with the results of the search. This code is incomplete, but I'm looking for conceptual fundamentals.

First, the interfaces in their separate unit:

type
  IAlbumResult = interface;
  IAlbumResults = interface;

  IAlbumResult = interface
    ['{3BC8FD07-CB40-472E-899F-041A87536B1D}']
    function GetOwner: IAlbumResults;
    function GetArtist: WideString;
    function GetID: Integer;
    function GetMBID: WideString;
    function GetName: WideString;
    function GetURL: WideString;
    property Owner: IAlbumResults read GetOwner;
    property Name: WideString read GetName;
    property Artist: WideString read GetArtist;
    property ID: Integer read GetID;
    property MBID: WideString read GetMBID;
    property URL: WideString read GetURL;
  end;

  IAlbumResults = interface
    ['{289B5852-31CB-4BB2-A276-B05EF644CF0D}']
    function Count: Integer;
    procedure Clear;
    procedure Delete(const Index: Integer);
    function GetItem(const Index: Integer): IAlbumResult;
    property Items[const Index: Integer]: IAlbumResult read GetItem; default;
  end;

And the implementation in the main wrapper unit:

  TAlbumResult = class(TInterfacedObject, IAlbumResult)
  private
    FOwner: IAlbumResults;
    FName: WideString;
    FArtist: WideString;
    FID: Integer;
    FMBID: WideString;
    FURL: WideString;
  public
    constructor Create(AOwner: TAlbumResults);
    destructor Destroy; override;
  public
    function GetOwner: IAlbumResults;
    function GetArtist: WideString;
    function GetID: Integer;
    function GetMBID: WideString;
    function GetName: WideString;
    function GetURL: WideString;
    property Owner: IAlbumResults read GetOwner;
    property Name: WideString read GetName;
    property Artist: WideString read GetArtist;
    property ID: Integer read GetID;
    property MBID: WideString read GetMBID;
    property URL: WideString read GetURL;
  end;

  TAlbumResults = class(TInterfacedObject, IAlbumResults)
  private
    FOwner: TLastFm;
    FItems: TInterfaceList;
    FPage: Integer;
    FPageCount: Integer;
    FTotal: Integer;
  public
    constructor Create(AOwner: TLastFm);
    destructor Destroy; override;
  public
    function Add: TAlbumResult;
    function Count: Integer;
    procedure Clear;
    procedure Delete(const Index: Integer);
    function GetItem(const Index: Integer): IAlbumResult;
    property Items[const Index: Integer]: IAlbumResult read GetItem; default;
  end;

implementation

{ TAlbumResult }

constructor TAlbumResult.Create(AOwner: TAlbumResults);
begin
  FOwner:= IAlbumResults(AOwner);
end;

destructor TAlbumResult.Destroy;
begin
  FOwner:= nil;
  inherited;
end;

function TAlbumResult.GetOwner: IAlbumResults;
begin
  Result:= FOwner;
end;

function TAlbumResult.GetArtist: WideString;
begin
  Result:= FArtist;
end;

function TAlbumResult.GetID: Integer;
begin
  Result:= FID;
end;

function TAlbumResult.GetMBID: WideString;
begin
  Result:= FMBID;
end;

function TAlbumResult.GetName: WideString;
begin
  Result:= FName;
end;

function TAlbumResult.GetURL: WideString;
begin
  Result:= FURL;
end;

{ TAlbumResults }

constructor TAlbumResults.Create(AOwner: TLastFm);
begin
  FOwner:= AOwner;
  FItems:= TInterfaceList.Create;
end;

destructor TAlbumResults.Destroy;
begin
  FItems.Free;
  inherited;
end;

function TAlbumResults.GetItem(const Index: Integer): IAlbumResult;
begin
  Result:= IAlbumResult(FItems[Index]);
end;

function TAlbumResults.Add: TAlbumResult;
begin
  Result:= TAlbumResult.Create(Self);

  FItems.Add(IAlbumResult(Result));
end;

procedure TAlbumResults.Clear;
begin
  while Count > 0 do
    Delete(0);
end;

function TAlbumResults.Count: Integer;
begin
  Result:= FItems.Count;
end;

procedure TAlbumResults.Delete(const Index: Integer);
begin
  FItems.Delete(Index);
end;
\$\endgroup\$
  • \$\begingroup\$ PS - is there some standard terminology behind this method of using interfaces? \$\endgroup\$ – Jerry Dodge Jul 9 '14 at 2:53
  • \$\begingroup\$ To be clear: This is real code, not stub or hypothetical? \$\endgroup\$ – Nic Hartley Nov 20 '15 at 23:18
  • \$\begingroup\$ @QPaysTaxes Yes, why would I go through all that work to write fake code? \$\endgroup\$ – Jerry Dodge Nov 21 '15 at 0:05
  • \$\begingroup\$ I'm not sure. The thing is, it had a close vote for stub/hypothetical code, and I wanted to clear that up. It might be worth clarifying in the question itself, too. \$\endgroup\$ – Nic Hartley Nov 21 '15 at 0:05
  • \$\begingroup\$ @QPaysTaxes I mean, it's not a full unit, the full unit would have been about 10,000 lines of code, mostly irrelevant to the question. Either way, this question was over a year old for a project I stopped working on, and also I've expanded my knowledge on this topic at hand too. \$\endgroup\$ – Jerry Dodge Nov 21 '15 at 0:07

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.