oop – 隐藏基类的虚方法有什么问题?

我一直在获得关于方法’创建’隐藏虚拟方法的Delphi编译器警告.

我已经回顾了几个Stack Overflow链接(见下文),我不明白这个警告背后的逻辑,以及为什么它被认为是错误的编码实践.我希望别人可以帮助我理解

我将包含一些示例代码

type    
  TMachine = class(TPersistent)
  private
  public
    Horsepower : integer;
    procedure Assign(Source : TMachine);
  end;

...

procedure TMachine.Assign(Source : TMachine);
begin
  inherited Assign(Source);
  Self.Horsepower := Source.HorsePower;
end;

这会导致编译器警告.

[dcc32 Warning] Unit1.pas(21): W1010 Method 'Assign' hides virtual method of base type 'TPersistent'

我一直忽视这个警告,因为它对我没有任何意义.但这让我以另一种方式陷入困境(请参阅我在这里发表的另一篇文章Why does Delphi call incorrect constructor during dynamic object creation?)所以我决定尝试更好地理解这一点.

我知道,如果我使用保留字重新引入,错误将消失,但我已经看到它反复发布这是一个坏主意.正如Warren P在这里写的那样(Delphi: Method ‘Create’ hides virtual method of base – but it’s right there),“恕我直言,如果你需要重新引入,你的代码闻起来很可怕”.

我想我明白“隐藏”是什么意思.正如David Heffernan在这里所说(What causes “W1010 Method ‘%s’ hides virtual method of base type ‘%s'” warning?):

What is meant by hiding is that from the derived class you no longer have access to the virtual method declared in the base class. You cannot refer to it since it has the same name as the method declared in the derived class. And that latter method is the one that is visible from the derived class.

我有点困惑,因为似乎祖先方法并没有真正隐藏,因为派生类总是只能使用inherited关键字来调用基类中的方法.所以’隐藏’真的意味着’有些隐藏’吗?

我想我也明白使用保留字覆盖将阻止编译器警告,但过程签名必须相同(即没有新添加的参数).我不能在这里使用它.

我不明白的是为什么隐藏是值得警告的.在上面的代码示例中,我不希望TMachine.Assign()的用户以某种方式使用TPersistent.Assign().在我的扩展课程中,我扩展了需求,因此希望他们使用新的和改进的功能.因此,隐藏旧代码似乎正是我想要的.我对虚方法的理解是基于运行时对象的实际类型调用正确方法方法.在这种情况下,我认为不应该有任何影响.

附加代码,将添加到上面的示例代码

TAutomobile = class(TMachine)
  public
    NumOfDoors : integer;
    constructor Create(NumOfDoors,AHorsepower : integer);
  end;

...

constructor TAutomobile.Create(ANumOfDoors,AHorsepower : integer);
begin
  Inherited Create(AHorsepower);
  NumOfDoors := ANumOfDoors;
end;

这会添加新的编译器警告消息:[dcc32警告] Unit1.pas(27):W1010方法’Create’隐藏基类型’TMachine’的虚方法

我特别不理解使用带有附加参数的新构造函数时出现的问题.在这文章(SerialForms.pas(17): W1010 Method ‘Create’ hides virtual method of base type ‘TComponent’)中,智慧似乎应该引入具有不同名称的构造函数,例如CreateWithSize.这似乎允许用户选择他们想要使用的构造函数.

如果他们选择旧的构造函数,扩展类可能会缺少一些创建所需的信息.但是,如果相反,我“隐藏”了先前的构造函数,那就是编程错误. Marjan Venema写了关于重新引入同一链接:重新引入打破多态性.这意味着您不能再使用元类(TxxxClass = Tyyy类)来实例化您的TComponent后代,因为它的Create将不会被调用.我根本不明白这一点.

也许我需要更好地理解多态性.托尼·斯塔克在这链接(What is polymorphism,what is it for,and how is it used?)中写道,多态性是:“面向对象编程的概念.不同对象以相同的方式以相同的方式响应的能力被称为多态.”那么我是否提出了一个不同的接口,即不再是相同的消息,从而打破了多态性?

我错过了什么?总之,在我的示例中,不是隐藏基本代码的好事吗?

解决方法

这里的危险是您可以在基类引用上调用Assign.因为您没有使用override,所以不会调用派生类方法.你已经破坏了多态性.

根据最少惊喜的原则,您应该在此处使用override,或者为您的派生类方法指定一个不同的名称.后一种选择很简单.前者看起来像这样:

type    
  TMachine = class(TPersistent)
  public
    Horsepower : integer;
    procedure Assign(Source : TPersistent); override;
  end;

...

procedure TMachine.Assign(Source : TPersistent);
begin
  if Source is TMachine then begin
    Horsepower := TMachine(Source).Horsepower;
  end else begin
    inherited Assign(Source);
  end;
end;

这允许您的班级与TPersistent的多态设计合作.不使用无法实现的覆盖.

您的下一个示例与虚拟构造函数类似.使构造函数成为虚拟的整个过程是,您可以在运行时直到创建实例而不知道它们的类型.规范示例是流式框架,即处理.dfm / .fmx文件并创建对象并设置其属性的框架.

流式传输框架依赖于TComponent的虚拟构造函数

constructor Create(AOwner: TComponent); virtual;

如果希望组件使用流式框架,则必须覆盖此构造函数.如果你隐藏它,那么流式框架找不到你的构造函数.

考虑流式传输框架如何实例化组件.它不知道它需要使用的所有组件类.例如,它不能考虑第三方代码,即您编写的代码. Delphi RTL无法知道那里定义的类型.流式框架实例化这样的组件:

type
  TComponentClass = class of TComponent;

var
  ClassName: string;
  Classtype: TComponentClass;
  NewComponent: TComponent;

....
ClassName := ...; // read class name from .dfm/.fmx file
Classtype := GetClass(ClassName); // a reference to the class to be instantiated
NewComponent := Classtype.Create(...); // instantiate the component

Classtype变量包含一个元类.这允许我们表示直到运行时才知道的类型.我们需要调用Create以多态方式调度,以便执行组件构造函数中的代码.除非在声明构造函数时使用override,否则它不会.

实际上,所有这些归结为多态性.如果您对多态性的理解不如您所建议的那么坚定,那么您将很难理解这一点.我认为你的下一步行动是更好地掌握多态性.

相关文章

 从网上看到《Delphi API HOOK完全说明》这篇文章,基本上都...
  从网上看到《Delphi API HOOK完全说明》这篇文章,基本上...
ffmpeg 是一套强大的开源的多媒体库 一般都是用 c/c+&#x...
32位CPU所含有的寄存器有:4个数据寄存器(EAX、EBX、ECX和ED...
1 mov dst, src dst是目的操作数,src是源操作数,指令实现的...