Передача интерфейса в качестве параметра

Возможно ли передать интерфейсный метод в качестве параметров?

Я пробую что-то вроде этого:

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;

Это должно позволить вызвать обычную процедуру.