【问题标题】:Passing Interface's method as parameter将接口的方法作为参数传递
【发布时间】:2010-10-16 20:10:06
【问题描述】:

接口的方法可以作为参数传递吗?

我正在尝试这样的事情:

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”和“过程、无类型指针或无类型参数”

当然,我可以为每个调用执行私有方法,但这很难看。有没有更好的办法?

德尔福 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's Visitor。现在我正在使用简单(“丑陋”)的包装器(我自己的,由 TOndrej 和 Aikislave 提供的相同解决方案)。

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

【问题讨论】:

  • TSomeObject.Move 中的代码看起来像是“策略”模式的用例。 MoveProc 可以是 TAbstractMoveProcStrategy 类的方法,其中子类在 Move 方法中实现所需的行为。 TMovePredStrategy / TMoveNextStrategy 会有不同的 Move procs。

标签: delphi oop interface callback


【解决方案1】:

如果您使用的是 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】:

我不知道您需要这样做的确切原因,但就个人而言,我认为传递整个“Mover”对象而不是它的一种方法会更好。我过去使用过这种方法,它被称为“访问者”模式。 tiOPF 是一个对象持久化框架,广泛使用它,并为您提供了一个很好的例子来说明它是如何工作的:The Visitor Pattern and the tiOPF

它相对较长,但对我来说非常有用,即使我没有使用 tiOPF。请注意文档中的第 3 步,标题为“第 3 步。我们将传递一个对象,而不是传递方法指针”。

DiGi,回答您的评论:如果您使用访问者模式,那么您没有实现多个方法的接口,而只有一个(执行)。然后,每个动作都有一个类,例如 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 函数不知道应该调用哪个方法。
【解决方案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 这行得通。但是必须注意,在调用函数时,对实现对象的引用仍然存在。对于一个工作类似但照顾参考的解决方案,请参阅my answer
【解决方案4】:

这个怎么样:

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;

【讨论】:

  • 那是我的“当然我可以为每个调用做私有方法,但这很丑”...
【解决方案5】:

这是另一个在 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;

【讨论】:

  • 这个很有前途
【解决方案6】:

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

已编辑... 抱歉,下一位,特别是“接口”位是开玩笑的。

另外,我在这里可能错了,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);

希望这会有所帮助。

【讨论】:

  • 过程移动(Const AInterface : ISomeInterface);开始 .... AInterface.Next;结束;
【解决方案7】:

您当前将 TMoveProc 定义为

TMoveProc = procedure of object;

尝试取出暗示隐藏的“this”指针作为第一个参数的“对象”。

TMoveProc = procedure;

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 1970-01-01
    • 2020-12-20
    • 2010-11-16
    相关资源
    最近更新 更多