将接口的方法作为参数传递

13

能否将接口的方法作为参数传递?

我正在尝试这样做:

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' 和 'procedure, untyped pointer or untyped parameter'

当然我可以为每个调用做一个私有方法,但那很丑陋。是否有更好的方法?

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,干净的解决方案是Diego的Visitor。现在我正在使用简单(“丑陋”的)包装器(我自己的,与TOndrej和Aikislave的解决方案相同)。

但真正的答案是,“没有(直接的)方法可以将接口的方法作为参数传递而不需要某种类型的提供者。”


TSomeObject.Move中的代码看起来像是“策略”模式的一个使用案例。MoveProc可以是TAbstractMoveProcStrategy类的一个方法,其中子类在Move方法中实现所需的行为。TMovePredStrategy / TMoveNextStrategy将具有不同的Move过程。 - mjn
7个回答

6
如果您正在使用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);

尝试仅传递接口方法的引用存在问题,因为您没有传递对接口本身的引用,因此无法对接口进行引用计数。但是,匿名方法本身是具有引用计数的,因此在此处匿名方法内部的接口引用也可以进行引用计数。这就是为什么此方法有效的原因。

2
好的,那你只能忍受“丑陋”了(或者升级!),因为一个破坏引用计数的“不丑陋”方法会更糟糕。 - Craig Stuntz

4
我不知道为什么你需要这样做,但是我个人认为传递整个"Mover"对象而不是其中一个方法会更好。我过去使用过这种方法,它被称为“访问者”模式。tiOPF,一个对象持久性框架,广泛使用它并为您提供了一个良好的示例:访问者模式和tiOPF
虽然相对较长,但它对我非常有用,即使我没有使用tiOPF。请注意文档中的第3步,标题为“步骤#3。我们将传递一个对象,而不是传递一个方法指针”。
DiGi,回答你的评论:如果您使用访问者模式,则没有实现多个方法的接口,而只有一个(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;

因为Move函数不知道应该调用哪个方法。 - DiGi

3
尽管包装类解决方案可行,但我认为这是一种过度设计。它太多代码了,而且你必须手动管理新对象的生命周期。
也许一个更简单的解决方案是在接口中创建返回 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;

+1 这个可以用。但是需要注意,在调用函数时,实现对象的引用仍然存在。如果想要一个类似但更加注重引用的解决方案,请参见我的答案 - yonojoy

2
这个怎么样?
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;

那是我的“当然我可以为每个调用做私有方法,但那很丑陋”… - DiGi

1

不行。由于接口的作用域,可能在调用.Next函数之前释放接口(也许?)。如果您想要这样做,应该将整个接口传递给您的方法,而不仅仅是一个方法。

编辑... 抱歉,下面的“Of Interface”部分是开玩笑的。

另外,我可能错了,i.Next不是Object的方法,根据您的类型定义,它应该是Interface的方法!

重新定义您的函数。

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

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

  O.Move(I);

希望这可以帮到你。

过程 Move(Const AInterface : ISomeInterface); 开始 .... AInterface.Next; 结束; - 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;

这非常有前途。 - DiGi

-1

您目前已定义TMoveProc为

TMoveProc = procedure of object;

尝试去掉“of object”,它暗示了一个隐藏的“this”指针作为第一个参数。

TMoveProc = procedure;

这应该允许调用正常的过程。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接