Передача метода интерфейса в качестве параметра - PullRequest
11 голосов
/ 01 апреля 2009

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

Я пытаюсь что-то вроде этого:

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


Edit: Я знаю, что могу передать весь интерфейс, но тогда я должен указать, какую функцию использовать. Я не хочу две абсолютно одинаковые процедуры с одним другим вызовом.

Я могу использовать второй параметр, но это тоже ужасно.

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).

Но верный ответ таков: «нет (прямого) способа передачи методов интерфейса в качестве параметров без какого-либо поставщика.

Ответы [ 7 ]

6 голосов
/ 01 апреля 2009

Если вы используете 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);

Проблема с попыткой передать ссылку только на метод интерфейса заключается в том, что вы не передаете ссылку на сам интерфейс, поэтому интерфейс не может быть подсчитан. Но анонимные методы сами подсчитывают ссылки, поэтому ссылка на интерфейс внутри анонимного метода также может быть подсчитана. Вот почему этот метод работает.

4 голосов
/ 01 апреля 2009

Я не знаю точную причину, по которой вам нужно это сделать, но лично я думаю, что было бы лучше передать весь объект "Mover" вместо одного из его методов. Я использовал этот подход в прошлом, он называется паттерном «посетитель». tiOPF, инфраструктура сохранения объектов, широко использует ее и дает хороший пример того, как она работает: Шаблон посетителя и tiOPF .

Это относительно долго, но оказалось очень полезным для меня, даже когда я не использовал tiOPF. Обратите внимание на шаг 3 в документе под названием « Шаг № 3. Вместо передачи указателя метода мы передадим объект ».

DiGi, чтобы ответить на ваш комментарий: Если вы используете шаблон Visitor, то у вас нет интерфейса, реализующего несколько методов, а только один (Выполнить). Затем у вас будет класс для каждого действия, например 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;
3 голосов
/ 23 октября 2012

Хотя решение класса обертки работает, я думаю, что это излишнее. Это слишком много кода, и вы должны вручную управлять временем жизни нового объекта.

Возможно, более простым решением было бы создание методов в интерфейсе, который возвращает 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;
2 голосов
/ 01 апреля 2009

Как насчет этого:

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;
1 голос
/ 24 июня 2014

Вот еще одно решение, которое работает в 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;
1 голос
/ 01 апреля 2009

Вы не можете. Из-за области действия Интерфейсов было бы возможно (возможно?) Для Интерфейса быть выпущенным прежде, чем Вы вызвали функцию .Next. Если вы хотите сделать это, вы должны передать весь интерфейс вашему методу, а не просто методу.

Отредактировано ... Извините, этот следующий бит, в частности бит "Of Interface", был задуман как шутка.

Кроме того, и я могу ошибаться, i.Next не является методом Object, согласно вашему типу def, это был бы метод Interface!

Переопределите вашу функцию

  TSomeObject = class(TObject)
  public
        procedure Move(Const AMoveIntf: ISomeInterface);
  end;

  Procedure TSomeObject.Move(Const AMoveIntf : ISomeInterface);
  Begin
       ....;
       AMoveIntf.Next;
  end;

  O.Move(I);

Надеюсь, это поможет.

0 голосов
/ 01 апреля 2009

В настоящее время TMoveProc определен как

TMoveProc = procedure of object;

Попробуйте вывести «объект», который подразумевает скрытый указатель «этот», в качестве первого параметра.

TMoveProc = procedure;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...