15

I realise that Delphi does not support interface helpers, but after reading several SO topics and sources of Spring4D and so forth, I'm wondering is there is any way to achieve the following? The source code comments pretty much sums up what I'm trying to do, so here it is:

program IHelper;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Spring,
  System.SysUtils;

type

  IMyThing = interface
  ['{01E799A5-9141-4C5E-AA85-B7C9792024D9}']
    procedure BasicThing;
  end;

  TMyThing = class(TInterfacedObject, IMyThing)
  strict private
    procedure BasicThing;
  end;

  IMyThingHelper = record
  private
    FOutage: IMyThing;
  public
    class operator Implicit(const Value: IMyThing): IMyThingHelper;
    procedure HelpfulThing;
  end;

  TMyThingHelper = class helper for TMyThing
  public
    class procedure ObjectThing;
  end;

{ TOutage }

procedure TMyThing.BasicThing;
begin
  Writeln('Basic Thing');
end;


{ IOutageHelper }

procedure IMyThingHelper.HelpfulThing;
begin
  Writeln('Helpful thing');
end;

class operator IMyThingHelper.Implicit(const Value: IMyThing): IMyThingHelper;
begin
  Result.FOutage := Value;
end;

{ TMyThingHelper }

class procedure TMyThingHelper.ObjectThing;
begin
  Writeln('Object thing');
end;

var
  LThing: IMyThing;

begin
  try
    LThing := TMyThing.Create;
    LThing.BasicThing;
    //LThing.HelpfulThing;               // <--- **** prefer this syntax but obviously does not compile
    IMyThingHelper(LThing).HelpfulThing; // <--- this works ok but prefer not to have to cast it here

    //LThing.ObjectThing;                // <--- obviously does not compile
    (LThing as TMyThing).ObjectThing;    // <--- class helpers work ok but no good for interfaces

    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

Any ideas or suggestions on how this code could be made to work where shown with **** ? I understand the answer might be an outright "no", but it seems that there's some pretty clever workarounds being done and perhaps someone much smarter than me knows how? (Delphi XE5)

Another example

var
   dataObject: IDataObject;

//Get clipboard IDataObject
OleGetClipboard({out}dataObject);

//Check if they want us to move or copy what's on the clipboard
preferredDropEffect: DWORD := dataObject.GetPreferredDropEffect;

//...do the stuff with the clipboard

//Tell them what we did
dataObject.SetPerformedDropEffect(DROPEFFECT_NONE); //we moved the underlying data; sender need not do anything
dataObject.SetPasteSucceeded(DROPEFFECT_MOVE); //Paste complete

with a helper:

TDataObjectHelper = interface helper for IDataObject
public
   function GetPreferredDropEffect(DefaultPreferredDropEffect: DWORD=DROPEFFECT_NONE): DWORD;
end;

function TDataObjectHelper.GetPreferredDropEffect(DefaultPreferredDropEffect: DWORD=DROPEFFECT_NONE): DWORD;
begin
{
    DROPEFFECT_NONE   = 0;  //Drop target cannot accept the data.
    DROPEFFECT_COPY   = 1;  //Drop results in a copy. The original data is untouched by the drag source.
    DROPEFFECT_MOVE   = 2;  //Drag source should remove the data.
    DROPEFFECT_LINK   = 4;  //Drag source should create a link to the original data.
    DROPEFFECT_SCROLL = 0x80000000 //Scrolling is about to start or is currently occurring in the target. This value is used in addition to the other values.
}
    if TDataObjectHelper.ContainsFormat(Source, CF_PreferredDropEffect) then
        Result := TDataObjectHelper.GetUInt32(Source, CF_PREFERREDDROPEFFECT)
    else
        Result := DefaultDropEffect;
end;
Ian Boyd
  • 246,734
  • 253
  • 869
  • 1,219
Rick Wheeler
  • 1,142
  • 10
  • 22
  • 1
    Perhaps I don't quite understand the problem, but what is wrong with simple (interface) inheritance? Make IMyThingHelper a descendant of IMyThing and have a TMyThingHelper *class*, implementing the extra functionality. – Rudy Velthuis Jul 17 '14 at 06:40
  • 1
    @Rudy The purpose of having an interface helper is that not every class that implements such interface has to implement that method aswell. Of course they can only operate on the API of the interface they are "helping". You should get familiar with the concept of extension methods. – Stefan Glienke Jul 17 '14 at 07:11
  • 3
    Yes, that would be cool if there were interface helpers. This solution merely emulates such helpers by wrapping the interface. That is a rather clumsy way of doing things, but I would implement the wrapper as class and have a helper *interface* that inherits from `IMyThing`. The implementation can still be a wrapper. But you could use such a derived interface in many situations where the original interface is required. – Rudy Velthuis Jul 17 '14 at 08:05
  • Rather than trying to shoe horn the language to do something it doesn't want to, what Rudy suggests in the comment above seems like the best compromise to me. – David Heffernan Jul 17 '14 at 08:15
  • @RudyVelthuis are you able to provide a sample or your proposed solution? – Rick Wheeler Jul 17 '14 at 10:28
  • Except for the new implementation, where I would use a wrapper, it would look much like Remy's solution. I don't have the time for a complete example now. – Rudy Velthuis Jul 17 '14 at 10:36
  • The problem with a wrapper opposed to a helper is that it is affected by modifications to the wrapped type. And it needs to route through all members of the wrapped type. – Stefan Glienke Jul 17 '14 at 10:40
  • @Stefan The problem with a helper is that it can't be used on an interface. – David Heffernan Jul 18 '14 at 06:20

2 Answers2

4

Why not just use another interface?

program IHelper;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  Spring,
  System.SysUtils;

type

  IMyThing = interface
  ['{01E799A5-9141-4C5E-AA85-B7C9792024D9}']
    procedure BasicThing;
  end;

  IMyThingHelper = interface
  ['{...}']
    procedure HelpfulThing;
  end;

  TMyThing = class(TInterfacedObject, IMyThing, IMyThingHelper)
  strict private
    procedure BasicThing;
    procedure HelpfulThing;
  end;

{ TOutage }

procedure TMyThing.BasicThing;
begin
  Writeln('Basic Thing');
end;

{ IOutageHelper }

procedure TMyThing.HelpfulThing;
begin
  Writeln('Helpful thing');
end;

var
  LThing: IMyThing;
  LHelper: IMyThingHelper;
begin
  try
    LThing := TMyThing.Create;
    LThing.BasicThing;
    if Supports(LThing, IMyThingHelper, LHelper) then
      LHelper.HelpfulThing;
    Readln;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 13
    Why not? Presumably because the asker is not in a position to modify the implementing object. That's why one uses helpers / extension methods. – David Heffernan Jul 17 '14 at 08:13
  • In such cases I use wrappers (I'm sure there is a better term or pattern name for this, I just don't know it). Not quite the same, but sufficient in many cases. – Rudy Velthuis Jul 17 '14 at 08:31
  • I use interface inheritance already but it is not what I'm trying to achieve at the moment. – Rick Wheeler Jul 17 '14 at 10:29
  • @Rick Remy is not suggesting interface inheritance. Not that I can see that helping you either. What Remy suggests here is not a substitute for helpers either. – David Heffernan Jul 17 '14 at 18:28
3

There are two ways of achieving this:

  • One would be having a variable of IMyThingHelper and assign your interface to it and then call the "extension method" on the record variable.

  • The other would be to use absolute:


var
  LThing: IMyThing;
  LHelper: IMyThingHelper absolute LThing;
begin
  LThing := TMyThing.Create;
  LHelper.HelpfulThing;

I blogged about this issue some while ago. Unfortunately in my case the "helper record" Enumerable<T> had so many generic methods that the compiler got slowed down immensely.

menjaraz
  • 7,551
  • 4
  • 41
  • 81
Stefan Glienke
  • 20,860
  • 2
  • 48
  • 102
  • 4
    `absolute` is an abomination and it would not wonder me if it were removed from the language one day. – Rudy Velthuis Jul 17 '14 at 06:41
  • Thanks Stefan, I have read your blog a few times and it gave me the head start, but it unfortunately seems that Delphi "just can't do it". I've no experience with the "absolute" keyword so I will need to investigate further. – Rick Wheeler Jul 17 '14 at 10:23