【问题标题】:Delphi: How to call inherited inherited ancestor on a virtual method?Delphi:如何在虚拟方法上调用继承的继承祖先?
【发布时间】:2011-06-07 11:00:02
【问题描述】:

我正在重写一个虚拟方法,我想调用 inherited。但我不想调用立即祖先,我想调用之前

TObject
   TDatabaseObject
      TADODatabaseObject <---call this guy
         TCustomer        <---skip this guy
            TVIP           <---from this guy

我尝试将 myself 转换为祖先,并在其上调用方法,但它导致递归堆栈溢出:

procedure TVip.SetProperties(doc: IXMLDOMDocument);
begin
   TADODatabaseObject(Self).SetProperties(doc); //skip over TCustomer ancestor
   ...
end;

我尝试添加 inherited 关键字,但编译失败:

procedure TVip.SetProperties(doc: IXMLDOMDocument);
begin
   inherited TADODatabaseObject(Self).SetProperties(doc); //skip over TCustomer ancestor
   ...
end;

可能吗?

【问题讨论】:

  • @Ian 我认为现在应该为你敲响警钟了!你系统这部分的设计不可能是对的。
  • @David Heffernan 你是对的。但是假设祖先是TListView,我真的无法重新设计一个我无法控制的类。
  • @Ian 你在什么时候有控制权? TADO 数据库对象?客户?
  • @David Heffernan 我提出了没人知道答案的棘手问题。
  • @Ian 哈哈。我想说虽然我(以及其他人)回答了你的问题stackoverflow.com/questions/4616535,但你不接受......

标签: delphi inheritance


【解决方案1】:

您可以使用获取虚拟方法的静态地址的技巧来做到这一点:

type
  TBase = class
    procedure Foo; virtual;
  end;

  TAnsestor = class(TBase)
    procedure Foo; override;
  end;

  TChild = class(TAnsestor)
    procedure Foo; override;
    procedure BaseFoo;
  end;

procedure TBase.Foo;
begin
  ShowMessage('TBase');
end;

procedure TAnsestor.Foo;
begin
  ShowMessage('TAnsestor');
end;

procedure TChild.Foo;
begin
  ShowMessage('TChild');
end;

type
  TFoo = procedure of object;

procedure TChild.BaseFoo;
var
  Proc: TFoo;

begin
  TMethod(Proc).Code := @TBase.Foo; // Static address
  TMethod(Proc).Data := Self;
  Proc();
end;

procedure TForm4.Button1Click(Sender: TObject);
var
  Obj: TChild;
  Proc: TFoo;

begin
  Obj:= TChild.Create;
  Obj.BaseFoo;
// or else
  TMethod(Proc).Code := @TBase.Foo; // Static address
  TMethod(Proc).Data := Obj;
  Proc();

  Obj.Free;
end;

【讨论】:

  • 这是一个很棒的技巧,我在这个场景中使用了它 - 我使用了 3rd 方框架,继承的方法做错了 - 吃掉我想要捕获的异常,所以我需要跳过它,重新实现一个稍微修改的逻辑,然后调用“祖父”(TBase)的方法。
【解决方案2】:

您不能以常规语言方式,因为这会破坏语言的面向对象方面。

你可以摆弄指针和聪明的演员来做到这一点,但在开始回答之前:这真的是你想要的吗?

正如其他人所说:您的需求听起来像是一种严重的“设计气味”(类似于code smell,但更严重。

编辑:

沿着指针摆弄的道路前进可能会在短期内节省您的工作量,但从长远来看可能会花费您数周的工作时间。
这有助于阅读:Upstream decisions, downstream costs

【讨论】:

  • 有趣的是,如果您尝试“跳转类层次结构”并且正在调用虚拟方法,则可能会出现无限递归(堆栈溢出时间!)。
  • 是的,这是一种严重的设计气味,但有时它可能是合理的,例如,当您知道类层次结构但对“妨碍”的类无能为力时。 (我不得不这样做一次以隐藏 TChart 中的某些 Windows 消息,同时仍然利用下面 TWinControl 中的默认处理。)user246408 下面的答案有效,是实现这一目标的最佳方法。
  • @IanGoldby 有趣的用例。您是否联系了 TChart 人员以解决问题?
  • @JeroenWiertPluimers 我怀疑他们是否会对这种特殊情况非常感兴趣。 (它改变了图表上鼠标点击的行为。)
  • 我刚刚有一个与另一个“黑客”相关的用例。我需要实现一个代码挂钩来更改虚拟方法中的某些 VCL 行为。不幸的是,替换需要调用它要替换的代码的祖先。 user246408 的选项是我能找到的唯一方法。
【解决方案3】:

我记得几年前我不得不做类似的事情来解决 VCL 层次结构的一些设计限制。

所以看起来是这样的:

type
  TGrandParent = class(TObject)
  public
    procedure Show;virtual;
  end;

  TParent = class(TGrandParent)
  public
    procedure Show;override;
  end;

  THackParent = class(TGrandParent)
  private
    procedure CallInheritedShow;
  end;

  TMyObject = class(TParent)
  public
    procedure Show;override;
  end;


{ TGrandParent }

procedure TGrandParent.Show;
begin
  MessageDlg('I''m the grandparent', mtInformation, [mbOk], 0);
end;

{ TParent }

procedure TParent.Show;
begin
  inherited;
  MessageDlg('I''m the parent', mtInformation, [mbOk], 0);
end;

{ THackParent }

procedure THackParent.CallInheritedShow;
begin
  inherited Show;
end;

{ TVIP }

procedure TMyObject.Show;
begin
  THackParent(Self).CallInheritedShow;
end;

procedure TForm6.Button6Click(Sender: TObject);
var
  VIP: TMyObject;
begin
  VIP:=TMyObject.Create;
  try
    VIP.Show;
  finally
    VIP.Free;
  end;
end;

不是超级优雅,但仍然是一个解决方案:)

【讨论】:

    【解决方案4】:

    如果您真的想这样做,那么您应该将您希望能够直接引用的继承层次结构部分提取到一个单独的受保护方法中。这将允许您从任何地方调用它,而不会破坏您的虚拟方法调度。

    但是,正如我所评论的,您的班级设计似乎有问题。

    【讨论】:

    • 对两者来说:设计有问题。但是祖先是罐装的并且可以正常工作。我可能最终会完全破坏它,在此过程中引入许多错误。我希望这一行代码可以节省我几天的工作时间。
    • 我遇到了类似的问题。基类是 30,000 行第三方组件代码。
    猜你喜欢
    • 1970-01-01
    • 2015-09-09
    • 1970-01-01
    • 2012-08-29
    • 2017-03-31
    • 2013-08-24
    • 1970-01-01
    相关资源
    最近更新 更多