Передача интерфейса в качестве параметра
Возможно ли передать интерфейсный метод в качестве параметров?
Я пробую что-то вроде этого:
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;
Но он не работает, потому что:
E2010 Несовместимые типы: "TMoveProc" и "процедура, нетипизированный указатель или нетипизированный параметр"
Конечно, я могу сделать частный метод для каждого вызова, но это уродливо. Есть ли лучший способ?
Delphi 2006
Изменить:
Я знаю, что могу передать весь интерфейс, но тогда я должен указать, какую функцию использовать. Я не хочу двух одинаковых процедур с одним разным вызовом.
Я могу использовать второй параметр, но это тоже уродливо.
type
SomeInterfaceMethod = (siPred, siNext)
procedure Move(SomeInt: ISomeInterface; Direction: SomeInterfaceMethod)
begin
case Direction of:
siPred: SomeInt.Pred;
siNext: SomeInt.Next
end;
end;
Спасибо всем за помощь и идеи. Чистым решением (для моего Delphi 2006) является Диего Посетитель. Теперь я использую простую ( "уродливую" ) обертку (мое собственное, то же самое решение от TOndrej и Aikislave).
Но верным ответом является "нет (прямого) способа передачи интерфейсных методов в качестве параметров без какого-либо провайдера.
Ответы
Ответ 1
Вот еще одно решение, которое работает в Delphi 20006. Это похоже на идею @Rafael, но с использованием интерфейсов:
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;
Ответ 2
Если вы использовали Delphi 2009, вы можете сделать это с помощью анонимного метода:
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);
Проблема с попыткой передать ссылку только на метод интерфейса заключается в том, что вы не передаете ссылку на сам интерфейс, поэтому интерфейс не может считаться ссылкой. Но анонимные методы сами подсчитываются, поэтому ссылка на интерфейс внутри анонимного метода здесь также может быть подсчитана. Вот почему этот метод работает.
Ответ 3
Я не знаю точной причины, по которой вам нужно это делать, но, лично, я думаю, было бы лучше передать весь объект "Mover" вместо одного из его методов. Я использовал этот подход в прошлом, он назывался шаблоном "Посетитель".
tiOPF, структура сохранения объектов, использует его широко и дает вам хороший пример того, как он работает: Шаблон посетителя и tiOPF.
Это относительно долго, но это оказалось очень полезным для меня, даже когда я не использовал tiOPF. Обратите внимание на шаг 3 в документе под названием "Шаг № 3. Вместо передачи указателя метода мы передадим объект".
DiGi, чтобы ответить на ваш комментарий: если вы используете шаблон Visitor, то у вас нет интерфейса, реализующего несколько методов, но только один (Execute). Тогда у вас будет класс для каждого действия, например, TPred, TNext, TSomething, и вы передаете экземпляр таких классов в объект, который нужно обработать. Таким образом, вам не нужно знать, что вызывать, вы просто вызываете "Visitor.Execute", и он будет выполнять эту работу.
Здесь вы можете найти базовый пример:
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;
Ответ 4
Несмотря на то, что решение класса обертки работает, я считаю, что это избыток. Это слишком много кода, и вам нужно вручную управлять временем жизни нового объекта.
Возможно, более простым решением было бы создание методов в интерфейсе, который возвращает TMoveProc
ISomeInterface = interface
...
function GetPredMeth: TMoveProc;
function GetNextMeth: TMoveProc;
...
end;
Класс, реализующий интерфейс, может предоставить procedure of object
, и он будет доступен через интерфейс.
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;
Ответ 5
Как насчет этого:
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;
Ответ 6
Вы не можете. Из-за видимости интерфейсов было бы возможно (возможно?), Чтобы интерфейс был выпущен до того, как вы вызвали функцию .Next. Если вы хотите сделать это, вы должны передать весь интерфейс вашему методу, а не только методу.
Под редакцией...
К сожалению, этот следующий бит, в частности, бит "Of Interface" означал в шутку.
Кроме того, и я могу ошибаться здесь, i.Next не является методом Object, так как ваш тип def, это будет метод интерфейса!
Переопределите свою функцию
TSomeObject = class(TObject)
public
procedure Move(Const AMoveIntf: ISomeInterface);
end;
Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface);
Begin
....;
AMoveIntf.Next;
end;
O.Move(I);
Надеюсь, что это поможет.
Ответ 7
В настоящее время TMoveProc определяется как
TMoveProc = procedure of object;
Попробуйте вывести "объект", который подразумевает скрытый указатель "this" как первый параметр.
TMoveProc = procedure;
Это должно позволить вызвать обычную процедуру.