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;
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.
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.
Related
I must first admit that I am from the .Net world and am currently relearning Delphi (XE 10.x) (from back in high school - MANY years ago). In .Net, the mediator pattern is fairly well handled by libraries such as MediatR or MassTransit. Yet, I have found very few libraries that support a dynamic (or semi-dynamic) implementation of the Mediator Pattern in Delphi. Without going to the fancy level of scanning the executing Rtti information, I wanted to create a simple mediator where I could register a CommandHandler by Request and then get a response back. Is this possible?
Here is some example code that I've made so far - but I'm just getting stuck on how to dynamically create the objects and whether my approach is even sound.
Before examining the code, I am not stuck on using a TDictionary<string, string> for registering the types, however, my limited knowledge of Rtti makes it difficult to figure out whether it should be using TClass or TRttiTypes. If either of those would be helpful, I would appreciate additional assistance on that.
// interface
uses
System.Generics.Collections;
type
TUnit = record
end;
IRequest<TResponse> = interface
end;
IRequest = interface(IRequest<TUnit>)
end;
IRequestHandler<TResponse; TRequest: IRequest<IResponse>> = interface(IInvokable)
function Handle(ARequest: TRequest): TResponse;
end;
IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
end;
TMediator = class
private
FRequestHandlers: TDictionary<string, string>;
public
constructor Create;
destructor Destroy; override;
procedure RegisterHandler(AHandlerClass, ARequestClass: TClass);
function Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
end;
// implementation
constructor TMediator.Create;
begin
Self.FRequestHandlers := TDictionary<string, string>.Create;
end;
destructor TMediator.Destroy;
begin
Self.FRequestHandlers.Free;
inherited;
end;
procedure TMediator.RegisterHandler(AHandlerClass, ARequestClass: TClass);
var
LTempRequestClass : string;
rContext : TRttiContext;
rType : TRttiType;
begin
if Self.FRequestHandlers.TryGetValue(ARequestClass.QualifiedClassName, LTempRequestClass) then
exit;
{ I would like to add some error checking functionality to prevent classes
that do not implement IRequest or IRequest<> from being added here. }
Self.FRequestHandlers.Add(ARequestClass.QualifiedClassName, AHandlerClass.QualifiedClassName);
end;
function TMediator.Send<TResponse, TRequest>(ARequest: TRequest): TResponse;
var
LRequestHandlerClassName: string;
LRequestHandler : IRequestHandler<TResponse, TRequest>;
begin
if not Self.FRequestHandlers.TryGetValue(ARequest.QualifiedClassName, LRequestHandlerClassName) then
raise Exception.Create('Handler class not registered with this mediator.');
{ Not sure what to do here to get the LRequestHandler - I'm also using Spring4d,
so I considered using the QualifiedClassName as a way to resolve classes
registered in the TContainer }
Result := LRequestHandler.Handle(ARequest);
end;
My anticipated usage of this would be:
NOTE: Edits below - I want to be able to register and call ANY commands that implement IRequest or IRequest<> from a single moderator.
// interface
type
TMyResponse = class
private
FFoo: string;
public
property Foo: string read FFoo write FFoo;
end;
TMyResponse2 = class
private
FFoo2: string;
public
property Foo2: string read FFoo2 write FFoo2;
end;
TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
private
FBar: string;
public
property Bar: string read FBar write FBar;
end;
TMyRequest2 = class(TInterfacedObject, IRequest<TMyResponse2>)
private
FBar2: string;
public
property Bar2: string read FBar2 write FBar2;
end;
TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
public
function Handle(ARequest: TMyRequest): TMyResponse;
end;
TMyRequestHandler2 = class(TInterfacedObject, IRequestHandler<TMyResponse2, TMyRequest2>)
public
function Handle(ARequest: TMyRequest2): TMyResponse2;
end;
// implementation
var
AMediator: TMediator;
ARequest: TMyRequest;
ARequest2: TMyRequest2;
AResponse: TMyResponse;
AResponse2: TMyResponse2;
begin
AMediator := TMediator.Create;
ARequest := TMyRequest.Create;
ARequest2 := TMyRequest2.Create;
try
ARequest.Bar := 'something';
ARequest2.Bar2 := 'else';
// Not sure how I would get these either - seems best to use the qualified class name
AMediator.Register(TMyRequestHandler.QualifiedClassName, TMyRequest.QualifiedClassName);
AMediator.Register(TMyRequestHandler2.QualifiedClassName, TMyRequest2.QualifiedClassName);
AResponse := AMediator.Send(ARequest);
AResponse2 := AMediator.Send(ARequest2);
// Do something with this value
finally
AResponse2.Free;
AResponse.Free;
ARequest2.Free;
ARequest.Free;
AMediator.Free;
end;
end.
So, it seems I was going about this the wrong way, thanks to J... who made me rethink what I was doing. In summary, I was trying to have something act as a layer of dependency injection to be able to dynamically run a "Handler" based on a given "Request". In the end, it appears that the simple solution was to call the Spring4d DI layer I was already using to perform the function. I still feel like there is some fairly tight coupling, but I am currently satisfied with the result. Here is the code:
CQRSU.pas
unit CQRSU;
interface
uses
System.Generics.Collections,
Spring.Container;
type
TUnit = record
end;
IBaseRequest = interface(IInvokable)
['GUID']
end;
IRequest<TResponse> = interface(IBaseRequest)
['GUID']
end;
IRequest = interface(IRequest<TUnit>)
['GUID']
end;
IRequestHandler<TResponse; TRequest: IRequest<TResponse>> = interface(IInvokable)
['GUID']
function Handle(ARequest: TRequest): TResponse;
end;
IRequestHandler<TRequest: IRequest<TUnit>> = interface(IRequestHandler<TUnit, TRequest>)
['GUID']
end;
implementation
end.
ServicesU.pas
unit ServicesU;
interface
uses
CQRSU;
type
TMyResponse = class
private
FMyResult: string;
public
property MyResult: string read FMyResult write FMyResult;
end;
TMyRequest = class(TInterfacedObject, IRequest<TMyResponse>)
private
FMyParameter: string;
public
property MyParameter: string read FMyParameter write FMyParameter;
end;
TMyRequestHandler = class(TInterfacedObject, IRequestHandler<TMyResponse, TMyRequest>)
public
function Handle(ARequest: TMyRequest): TMyResponse;
end;
implementation
{ TMyRequestHandler }
function TMyRequestHandler.Handle(ARequest: TMyRequest): TMyResponse;
begin
Result := TMyResponse.Create;
Result.MyResult := ARequest.MyParameter + ' Processed';
end;
end.
TestCQRS.dpr
program TestCQRS;
{$APPTYPE CONSOLE}
{$R *.res}
uses
Spring.Container,
System.SysUtils,
CQRSU in 'CQRSU.pas',
ServicesU in 'ServicesU.pas';
var
LContainer: TContainer;
LMyRequestHandler: IRequestHandler<TMyResponse, TMyRequest>;
LRequest: TMyRequest;
LResponse: TMyResponse;
begin
LContainer := TContainer.Create;
try
LRequest := TMyRequest.Create;
LRequest.MyParameter := 'Hello there!';
try
LContainer.RegisterType<TMyRequestHandler>.Implements<IRequestHandler<TMyResponse, TMyRequest>>;
LContainer.Build;
LMyRequestHandler := LContainer.Resolve<IRequestHandler<TMyResponse, TMyRequest>>;
LResponse := LMyRequestHandler.Handle(LRequest);
writeln(LResponse.MyResult);
readln;
except
on E: Exception do
writeln(E.ClassName, ': ', E.Message);
end;
finally
if Assigned(LResponse) then
LResponse.Free;
if Assigned(LRequest) then
LRequest.Free;
LContainer.Free;
end;
end.
The game i'm trying to make is snake, in the console application. I can get the snake to move along the screen however I am not sure how I can read the user inputing the keys WASD, code segment shown below.
write (StoredTrail); //This would be writing the snake, each segment is '[]'
repeat
clearScreen; // This is calling a clear screen procedure, if there is a simple way to make the snake disappear from the console that avoids such a lengthy procedure that would be great to know.
delete (StoredTrail ,0,2);
StoredTrail:= A+StoredTrail; //This makes the trail move along(A is ' ')
write(StoredTrail);
Xcord:= Xcord + 1;
sleep(150);
until 1=2;
I am also aware the sleep is very inefficient so if anyone had a better way to delay the movement of the snake that would also be welcomed. Coding for increasing the snakes length is also not implemented yet.
Many thanks to anyone able to help.
I give an example for a event driven console application, which update the screen iterativelly.
It would be too long to write here the user event handler routines and you can find it on a lot of places on the net. This is a fine example, which handle keyboard and mouse events as well:
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils
, Vcl.ExtCtrls
;
type
TSnakeApp = class
private
fTimer : TTimer;
fExit : boolean;
protected
function createTimer : TTimer; virtual;
procedure releaseTimer; virtual;
procedure drawSnake( timer_ : TObject ); virtual;
procedure handleKeyBoardEvents; virtual;
public
constructor create;
destructor destroy; override;
procedure run;
end;
var
app : TSnakeApp;
function TSnakeApp.createTimer : TTimer;
begin
result := TTimer.Create( NIL );
end;
procedure TSnakeApp.releaseTimer;
begin
fTimer.Free;
end;
procedure TSnakeApp.drawSnake( timer_ : TObject );
begin
// if it takes too long time (>= times.interval), then disable+enable the timer
fTimer.enabled := FALSE;
try
finally
fTimer.enabled := TRUE;
end;
end;
procedure TSnakeApp.handleKeyBoardEvents;
begin
// It would be too long to write here, but you can find a very nice keyboard/mouse event handler for console applications here:
// https://learn.microsoft.com/en-us/windows/console/reading-input-buffer-events
// case ( keyPressed ) of
// VK_ESC:
// fExit := TRUE;
// ...
end;
constructor TSnakeApp.create;
begin
inherited create;
fTimer := createTimer;
fTimer.Interval := 20;
fTimer.OnTimer := drawSnake;
end;
destructor TSnakeApp.destroy;
begin
releaseTimer;
inherited destroy;
end;
procedure TSnakeApp.run;
begin
fTimer.enabled := TRUE;
while ( not fExit ) do
begin
handleKeyBoardEvents;
end;
fTimer.enabled := FALSE;
end;
begin
try
try
app := TSnakeApp.create;
app.run;
finally
app.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
In the days of Turbo Pascal an ancient predecessor of Delphi there was a CRT unit that provided some useful functions for console applications. Two such functions that would be of interest to you for keyboard input are KeyPressed() which returns true if a key has been pressed and GetKey() which returns the key pressed. For Delphi itself there are a few sources of libraries that offer compatible functions. One is Rudy's Velthuis.Console unit.
I wonder if I have found an Embarcadero compiler bug ...
The problem looks like it is related to generics.
Here is my source code
unit u_DateCount;
interface
uses
SysUtils,
u_JavaScriptable
;
type
TDateCount = class (TJavaScriptable)
strict private
public
NoOfSamples : Integer;
TheDate : TDate;
function ToString():String; override;
end;
implementation
function TDateCount.ToString():String;
var
myYear, myMonth, myDay : Word;
begin
DecodeDate(TheDate, myYear, myMonth, myDay);
Result := Format('[new Date(%d, %d ,0), %d]', [myYear, myMonth, NoOfSamples]);
end;
end.
unit u_Javascriptable;
interface
type
TJavaScriptable = class
strict private
public
function ToString:String; override;
end;
implementation
function TJavaScriptable.ToString:String;
begin
Result := '';
end;
end.
unit u_LineChart;
interface
uses
System.IOUtils,
SysUtils,
System.Generics.Collections,
u_JavaScriptable
;
type
TLineChart<RecordType : TJavaScriptable> = class
strict private
Template : String;
function ConvertRecordsToString():String;
public
Records : TList<RecordType>;
function ToString():String;
constructor Create(templatePath : String);
destructor Destroy(); override;
end;
implementation
function TLineChart<RecordType>.ConvertRecordsToString():String;
var
I: Integer;
begin
//Open brackets
Result := '[ ';
//The first record
if Records.Count > 0 then
begin
Result := Result + Records[0].ToString();
end;
//Loop over records
for I := 1 to Records.Count - 1 do
begin
Result := Result + ', ' + Records[I].ToString();
end;
//Close bracket
Result := Result + ' ]';
end;
function TLineChart<RecordType>.ToString():String;
begin
Result := Format(Template, [ConvertRecordsToString()]);
end;
constructor TLineChart<RecordType>.Create(templatePath : String);
begin
inherited Create();
Template := TFile.ReadAllText(templatePath);
Records := TList<RecordType>.Create();
end;
destructor TLineChart<RecordType>.Destroy();
var
I: Integer;
begin
if Assigned(Records) then
begin
for I := 0 to Records.Count - 1 do
begin
Records[I].Destroy();
end;
Records.Clear();
Records.Destroy();
Records := nil;
end;
inherited;
end;
end.
And finally the main program
program Project4;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils,
u_Javascriptable in 'u_Javascriptable.pas',
u_LineChart in 'u_LineChart.pas',
u_DateCount in 'u_DateCount.pas';
var
lineChart : TLineChart<TDateCount>;
begin
lineChart := TLineChart<TDateCount>.Create('linechart.html');
try
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
The error message I get when I try to compile this is
[dcc32 Fatal Error] Project4.dpr(30): F2084 Internal Error:
AV097530AC-R00000014-0
Usually when I see an error message similar to this, I can fix it by closing the embarcadero IDE and restarting it. However this did not seem to work this time.
The problem is in the implementation of TLineChart<RecordType>.Destroy().
Change Records[I].Destroy(); to Records[I].Free(); and it works.
Or you just do it correct and use TObjectList<RecordType>.Create; in the constructor which takes care of destroying all elements in it when destroying the list.
Never call Destroy directly. Use Free. While it should not result in a compiler error it is wrong anyway.
If the compiler reports an "internal error," that's always a compiler bug. You should open a ticket in QC for this. Hopefully they can get it fixed for XE5.
Since this works in XE3 but not XE4, I'm going to presume this is an XE4 bug. Until this is fixed, the solution is to use a different version of the compiler such as XE3.
Is it possible to obtain RTTI information about a TMethod?
I can get the instance by
Instance := TObject(Method.Data);
so I can get the RTTI type of the instance, but how can I get the correct TRttiMethod? I want to check for attributes on a method passed in using a method pointer.
This approach works in theory, and there's a good change it will work in practice, but there are a couple of things that could prevent you from getting hold of the TRttiMethod.
The TMethod record says Data: Pointer, not TObject. This implies there might be a possibility of having something other then an TObject as the Data! This is a serious issue, because if the Data is not TObject, then attempting to extract RTTI from it is going to result in runtime errors.
Not all methods have RTTI. By default methods in the private area do not have RTTI, and one can use the {$RTTI} to stop generating RTTI for public or published members as well.
Those two issues would not be a problem for the usual type of event implementations we have in Delphi (double-click on the name of the event in Object Inspector and fill in the code), but then again I don't think you're talking about "vanila" implementations. Not many people would decorate the default event handlers with Attributes!
Code that demonstrates all of the above:
program Project15;
{$APPTYPE CONSOLE}
uses
SysUtils, RTTI;
type
// Closure/Event type
TEventType = procedure of object;
// An object that has a method compatible with the declaration above
TImplementation = class
private
procedure PrivateImplementation;
public
procedure HasRtti;
procedure GetPrivateImpEvent(out Ev:TEventType);
end;
TRecord = record
procedure RecordProc;
end;
// an object that has a compatible method but provides no RTTI
{$RTTI EXPLICIT METHODS([])}
TNoRttiImplementation = class
public
procedure NoRttiAvailable;
end;
procedure TImplementation.GetPrivateImpEvent(out Ev:TEventType);
begin
Ev := PrivateImplementation;
end;
procedure TImplementation.HasRtti;
begin
WriteLn('HasRtti');
end;
procedure TNoRttiImplementation.NoRttiAvailable;
begin
WriteLn('No RTTI Available');
end;
procedure TRecord.RecordProc;
begin
WriteLn('This is written from TRecord.RecordProc');
end;
procedure TImplementation.PrivateImplementation;
begin
WriteLn('PrivateImplementation');
end;
procedure TotalyFakeImplementation(Instance:Pointer);
begin
WriteLn('Totaly fake implementation, TMethod.Data is nil');
end;
procedure SomethingAboutMethod(X: TEventType);
var Ctx: TRttiContext;
Typ: TRttiType;
Method: TRttiMethod;
Found: Boolean;
begin
WriteLn('Invoke the method to prove it works:');
X;
// Try extract information about the event
Ctx := TRttiContext.Create;
try
Typ := Ctx.GetType(TObject(TMethod(X).Data).ClassType);
Found := False;
for Method in Typ.GetMethods do
if Method.CodeAddress = TMethod(X).Code then
begin
// Got the Method!
WriteLn('Found method: ' + Typ.Name + '.' + Method.Name);
Found := True;
end;
if not Found then
WriteLn('Method not found.');
finally Ctx.Free;
end;
end;
var Ev: TEventType;
R: TRecord;
begin
try
try
WriteLn('First test, using a method that has RTTI available:');
SomethingAboutMethod(TImplementation.Create.HasRtti);
WriteLn;
WriteLn('Second test, using a method that has NO rtti available:');
SomethingAboutMethod(TNoRttiImplementation.Create.NoRttiAvailable);
WriteLn;
WriteLn('Third test, private method, default settings:');
TImplementation.Create.GetPrivateImpEvent(Ev);
SomethingAboutMethod(Ev);
WriteLn;
WriteLn('Assign event handler using handler from a record');
try
SomethingAboutMethod(R.RecordProc);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
WriteLn('Assign event handler using static procedure');
try
TMethod(Ev).Data := nil;
TMethod(Ev).Code := #TotalyFakeImplementation;
SomethingAboutMethod(Ev);
except on E:Exception do WriteLn(E.Message);
end;
WriteLn;
except
on E: Exception do Writeln(E.ClassName, ': ', E.Message);
end;
finally ReadLn;
end;
end.
Is it possible to pass interface's method as parameters?
I'm trying something like this:
interface
type
TMoveProc = procedure of object;
// also tested with TMoveProc = procedure;
// procedure of interface is not working ;)
ISomeInterface = interface
procedure Pred;
procedure Next;
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TMoveProc);
end;
implementation
procedure TSomeObject.Move(MoveProc: TMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(i.Next);
// somewhere else: o.Move(i.Prev);
// tested with o.Move(#i.Next), ##... with no luck
o.Free;
end;
But it is not working because:
E2010 Incompatible types: 'TMoveProc' and 'procedure, untyped pointer or untyped parameter'
Of course I can do private method for each call, but that is ugly. Is there any better way?
Delphi 2006
Edit:
I know that I can pass whole interface, but then I have to specify which function use. I don't want two exactly same procedures with one different call.
I can use second parameter, but that is ugly too.
type
SomeInterfaceMethod = (siPred, siNext)
procedure Move(SomeInt: ISomeInterface; Direction: SomeInterfaceMethod)
begin
case Direction of:
siPred: SomeInt.Pred;
siNext: SomeInt.Next
end;
end;
Thanks all for help and ideas. Clean solution (for my Delphi 2006) is Diego's Visitor. Now I'm using simple ("ugly") wrapper (my own, same solution by TOndrej and Aikislave).
But true answer is "there is no (direct) way to pass interface's methods as parameters without some kind of provider.
If you were using Delphi 2009, you could do this with an anonymous method:
TSomeObject = class(TObject)
public
procedure Move(MoveProc: TProc);
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := GetSomeInterface;
o.Move(procedure() begin i.Next end);
The problem with trying to pass a reference to just the interface method is that you are not passing a reference to the interface itself, so the interface cannot be reference counted. But anonymous methods are themselves reference counted, so the interface reference inside the anonymous method here can be reference counted as well. That is why this method works.
I don't know the exact reason why you need to do that, but, personally, I think it would be better to pass the whole "Mover" object instead of one of its methods. I used this approach in the past, it's called "Visitor" pattern.
tiOPF, an object persistence framework, uses it extensively and gives you a good example of how it works: The Visitor Pattern and the tiOPF.
It's relatively long, but it proved very useful to me, even when I didn't use tiOPF. Note step 3 in the document, titled "Step #3. Instead of passing a method pointer, we will pass an object".
DiGi, to answer your comment: If you use Visitor pattern, then you don't have an interface implementing multiple methods, but just one (Execute). Then you'd have a class for each action, like TPred, TNext, TSomething, and you pass an instance of such classes to the object to be processed. In such way, you don't have to know what to call, you just call "Visitor.Execute", and it will do the job.
Here you can find a basic example:
interface
type
TVisited = class;
TVisitor = class
procedure Execute(Visited: TVisited); virtual; abstract;
end;
TNext = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TPred = class(TVisitor)
procedure Execute (Visited: TVisited); override;
end;
TVisited = class(TPersistent)
public
procedure Iterate(pVisitor: TVisitor); virtual;
end;
implementation
procedure TVisited.Iterate(pVisitor: TVisitor);
begin
pVisitor.Execute(self);
end;
procedure TNext.Execute(Visited: TVisited);
begin
// Implement action "NEXT"
end;
procedure TPred.Execute(Visited: TVisited);
begin
// Implement action "PRED"
end;
procedure Usage;
var
Visited: TVisited;
Visitor: TVisitor;
begin
Visited := TVisited.Create;
Visitor := TNext.Create;
Visited.Iterate(Visitor);
Visited.Free;
end;
Although the wrapper class solution works, I think that's an overkill. It's too much code, and you have to manually manage the lifetime of the new object.
Perhaps a simpler solution would be to create methods in the interface that returns TMoveProc
ISomeInterface = interface
...
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
...
end;
The class that implements the interface can provide the procedure of object and it will be accessible through the interface.
TImplementation = class(TInterfaceObject, ISomeInterface)
procedure Pred;
procedure Next;
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
end;
...
function TImplementation.GetPredMeth: TMoveProc;
begin
Result := Self.Pred;
end;
function TImplementation.GetNextMeth: TMoveProc;
begin
Result := Self.Next;
end;
How about this:
type
TMoveProc = procedure(const SomeIntf: ISomeInterface);
TSomeObject = class
public
procedure Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
end;
procedure TSomeObject.Move(const SomeIntf: ISomeInterface; MoveProc: TMoveProc);
begin
MoveProc(SomeIntf);
end;
procedure MoveProcNext(const SomeIntf: ISomeInterface);
begin
SomeIntf.Next;
end;
procedure MoveProcPred(const SomeIntf: ISomeInterface);
begin
SomeIntf.Pred;
end;
procedure Usage;
var
SomeObj: TSomeObject;
SomeIntf: ISomeInterface;
begin
SomeIntf := GetSomeInterface;
SomeObj := TSomeObject.Create;
try
SomeObj.Move(SomeIntf, MoveProcNext);
SomeObj.Move(SomeIntf, MoveProcPred);
finally
SomeObj.Free;
end;
end;
Here is another solution that is working in Delphi 20006. It is similar to the idea of #Rafael, but using interfaces:
interface
type
ISomeInterface = interface
//...
end;
IMoveProc = interface
procedure Move;
end;
IMoveProcPred = interface(IMoveProc)
['{4A9A14DD-ED01-4903-B625-67C36692E158}']
end;
IMoveProcNext = interface(IMoveProc)
['{D9FDDFF9-E74E-4F33-9CB7-401C51E7FF1F}']
end;
TSomeObject = class(TObject)
public
procedure Move(MoveProc: IMoveProc);
end;
TImplementation = class(TInterfacedObject,
ISomeInterface, IMoveProcNext, IMoveProcPred)
procedure IMoveProcNext.Move = Next;
procedure IMoveProcPred.Move = Pred;
procedure Pred;
procedure Next;
end;
implementation
procedure TSomeObject.Move(MoveProc: IMoveProc);
begin
while True do
begin
// Some common code that works for both procedures
MoveProc.Move;
// More code...
end;
end;
procedure Usage;
var
o: TSomeObject;
i: ISomeInterface;
begin
o := TSomeObject.Create;
i := TImplementation.Create;
o.Move(i as IMoveProcPred);
// somewhere else: o.Move(i as IMoveProcNext);
o.Free;
end;
You can't. Because of the scoping of Interfaces it would be possible (perhaps?) for the Interface to be released before you called the .Next function. If you want to do this you should pass the whole interface to your method rather than just a method.
Edited...
Sorry, this next bit, specifically the "Of Interface" bit was meant in jest.
Also, and I could be wrong here, i.Next is not a method Of Object, as per your type def, it would be a method Of Interface!
Redefine your function
TSomeObject = class(TObject)
public
procedure Move(Const AMoveIntf: ISomeInterface);
end;
Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface);
Begin
....;
AMoveIntf.Next;
end;
O.Move(I);
Hope this helps.
You currently have TMoveProc defined as
TMoveProc = procedure of object;
Try taking out the "of object" which implies a hidden "this" pointer as first parameter.
TMoveProc = procedure;
That should allow a normal procedure to be called.