覆盖构造函数错误

3

我刚进入Delphi编程领域,试图覆盖一个构造函数时出现错误,请问我做错了什么或者应该怎么做才能得到想要的结果。

我想覆盖一个框架(Frame)的构造函数,使其将其中一个标签(Label)的标题(Caption)更改为特定的文本。

以下是代码:

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TfrmMesaj = class(TFrame)
    Panel1: TPanel;

  private
    { Private declarations }
  public
    { Public declarations }
    constructor Create(name : string);  override;
  end;

implementation

{$R *.dfm}

{ TfrmMesaj }



{ TfrmMesaj }

constructor TfrmMesaj.Create(name: string);
begin
  inherited;
   Panel1.Color := clRed;
   Panel1.Caption := name;
end;

end.

当我尝试编译时,出现以下错误:

[DCC Error] frameMesaj.pas(17): E2037 Declaration of 'Create' differs from previous declaration
[DCC Error] frameMesaj.pas(32): E2008 Incompatible types

我做错了什么,如何达到我想要的结果?

如果我这样做,就会得到以下错误信息:[DCC Error] frameMesaj.pas(32): E2035 实际参数不足。 - CiucaS
https://en.delphipraxis.net/topic/979-class-inheritance-and-hides-method/ - Gabriel
3个回答

9

Stefan已经解释了为什么你的覆盖方法不起作用。基本上,每当你覆盖一个虚方法时,这两个方法的签名必须完全匹配。但是,我强烈反对使用reintroduce。我会在本答案底部解释原因。(请注意,reintroduce非常明确地不会覆盖祖先方法。它只是隐藏警告,说明该方法正在隐藏祖先的方法。)

几个更好的选项:

为你的构造函数使用不同的名称

你不必将构造函数命名为Create。例如,你可以添加第二个构造函数:constructor CreateWithCaption(AName: string);。请注意,我甚至没有使此构造函数成为虚方法。只有当你打算让它们表现出多态性时,才使方法成为虚方法。(这意味着即使从基类调用,你也希望子类能够更改实现。)

这个选项非常像 Stefan 建议的 overload

使用工厂方法创建你的框架

随着系统变得越来越大,将某些对象的创建过程与它们实际完成的工作分离开来可能非常有用。这是通过使用工厂方法来实现的,其唯一目的是创建其他准备好与你的系统交互的对象。例如:

//I've chosen to demonsrate this on a form, but you could also implement a dedicated factory class
function TMyForm.CreateMessageFrame(ACaption: string): TFrame;
begin
  //Note the factory method intends the form to own all frames created.
  Result := TfrmMesaj.Create(Self);
  
  //The factory method ensures the frame is "ready"
  //This does violate Law of Demeter, but you could easily add a method to the fram to resolve that.
  Result.Panel1.Color := clRed;
  Result.Panel1.Caption := ACaption;
end;

重写方法,为什么reintroduce是有问题的?

  • reintroduce只适用于基类中的虚方法。
  • 这个方法应该只在有多态需求时才需要声明为虚方法。
  • 这意味着该方法旨在从基类引用调用,但可能需要在某些子类中采取特殊操作以使其正常工作。

由于您的问题涉及覆盖TComponent.Create,因此将通过示例来说明。

  • constructor TComponent.Create(AOwner: TComponent);明确地声明为虚方法,以便组件创建可以进行多态性操作。这样,当组件从.DFM文件流中读取时,即使使用的引用类型是TComponent,它们也会被正确创建。
  • 如果隐藏此构造函数,则无法执行在从.DFM文件流中创建您的框架时需要执行的任何特殊操作。
  • 此外,您的框架的任何子类都无法覆盖constructor Create(AOwner: TComponent);,因为它被隐藏了。

1
请注意,重载Create可能有原因:如果该类也要在C++Builder中使用。在Delphi中,您可以拥有构造函数Create(Integer)CreateOtherWay(Integer)。但是,在C++中,这将导致名称冲突,并且您的类将无法在C++Builder中使用。我完全赞成为不同的目的使用不同的构造函数名称,但不要忘记,如果需要C++Builder兼容性,则您的操作并不完全自由。 - Rudy Velthuis

5
TFrame 的构造函数如下所示:
constructor Create(AOwner: TComponent); virtual;

如果您想要进行覆盖,则必须保留签名:

constructor Create(AOwner: TComponent); override;

如果您想添加自己带有名称的版本,您需要进行重载,以便两个版本并存:

constructor Create(name: string); reintroduce; overload; // see Edit below 

如果您想隐藏虚拟元素,则需要编写reintroduce(不建议使用):

constructor Create(name: string); reintroduce;

编辑: 即使不隐藏虚拟方法,当你重载虚拟方法时,也需要使用reintroduce。这已经在这里报告和讨论:http://qc.embarcadero.com/wc/qcmain.aspx?d=106026


3
请不要推荐使用“reintroduce”,因为它会破坏基类旨在实现子类的多态行为能力。该选项仅用于解决库的新版本向现有基类添加方法时出现名称冲突的情况。即使考虑到这个目的,这个选项也有点不成熟,因为它只是隐藏了潜在的未来问题。但是,在创建全新的子类时,绝对没有理由不选择不同的名称,从而完全避免潜在的问题。 - Disillusioned
1
我并没有推荐任何东西,只是列出了不同的修饰符及其用途。但是我同意关于破坏多态性会违反OCP和LSP的观点。 - Stefan Glienke
你没有推荐它,但你提供了这个选项,却没有同时呈现其缺点。 - David Heffernan
我敢说,当像您这样的专家“仅仅提到”一个选项时,初学者会将其视为推荐。 - Disillusioned
请注意,QualityCentral现已关闭,因此您无法再访问qc.embarcadero.com链接。如果您需要访问旧的QC数据,请查看QCScraper - Remy Lebeau

0
为什么不直接使用OnCreate事件,而要试图覆盖默认的构造函数方法呢?
在您的情况下,OnCreate事件就足够了,因为您并没有对TFrame组件本身进行任何关键更改,而只是对已放置在其上的其他组件进行更改。

因为TFrame没有onCreate事件。 - CiucaS
你如何指定在 OnCreate 事件中使用的字符串值?此外,OnCreate 给了你一种鸡生蛋和蛋生鸡的问题: 在你创建某些东西之前,你无法挂钩 "OnCreate",但当它被创建时,做一些事情已经太晚了。它在 DFMs 中有效的唯一原因是因为它内置于流式传输机制中。如果这样的话,为什么不直接流传正确的标题值呢? - Disillusioned

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