Delphi 多态性及其性质

Delphi 多态性及其性质,delphi,freepascal,Delphi,Freepascal,我试图使用多态性并创建一个属性,该属性根据对象的类型返回不同的类型 为什么下面的代码会出现此编译器警告: [警告:使用抽象方法“SetValue”构造类“TTimeField”] 更重要的是,在创建TTimeField时,我如何使此工作正常,以便为值分配TDateTime?实际上,有一条关于“类型不匹配”的消息 实施 function TTimeField.GetValue: TDateTime; begin Result:= FValue; end; procedure TTimeFie

我试图使用多态性并创建一个属性,该属性根据对象的类型返回不同的类型

为什么下面的代码会出现此编译器警告:

[警告:使用抽象方法“SetValue”构造类“TTimeField”]

更重要的是,在创建TTimeField时,我如何使此工作正常,以便为值分配TDateTime?实际上,有一条关于“类型不匹配”的消息

实施

function TTimeField.GetValue: TDateTime;
begin
  Result:= FValue;
end;

procedure TTimeField.SetValue(AValue: TDateTime);
begin
  FValue:= AValue;
end;
编辑:下面,通过使用字符串,我实现了所需的功能,但是如果上面的代码起作用,那么就意味着性能有所提高

TBaseField = class
private
  procedure SetStrValue(AValue: string); virtual; abstract;
  function  GetStrValue: string; virtual; abstract;
public
  property  AsString: string read GetStrValue write SetStrValue;
end;

TTimeField = class(TBaseField)
private
  FValue :  TDateTime;
  function  GetStrValue: string; override;
  procedure SetStrValue(AValue: string); override;
public
  property  AsString: string read GetStrValue write SetStrValue;
end;

function TTimeField.GetStrValue: string;
begin
  Result:= DateTimeToStr(FValue);
end;

procedure TTimeField.SetStrValue(AValue: string);
begin
  FValue:=StrToDateTime(AValue);
end;

我认为你的问题在于你实际上没有覆盖任何方法;您的新方法具有不同的签名。因此,它实际上是一个过载。在您的示例中,拥有
TBaseField
对您没有任何帮助。子类不使用它的任何方法,也不提供任何额外的功能

多态性是指给定接口(如方法签名)的不同实现,而调用方不需要知道这一点。 看看您的示例,您可能不一定在寻找多态性,而是在寻找一种方法,用尽可能少的代码为各种数据类型实现类似的方法/属性。这就是泛型通常更有用的地方

假设所有字段类都需要存储自己类型的值,应该可以设置和检索该值。然后您可以定义一个通用的
T字段
,其中
T
是该字段应使用的数据类型的“占位符”。您将能够为它提供一个getter和setter,以及一个属性,这也是通用的。因此,对于最终使用的所有类型的字段,只需定义一次。这些方法可能包含所有字段共享的任何逻辑。但是,您仍然可以对泛型类型进行子类化,以向特定类型添加特定功能,或者隐藏依赖泛型的事实

一个简单的例子:

type
  TField<T> = class
  private
    FValue: T;
  protected
    function GetValue: T; virtual;
    procedure SetValue(const AValue: T); virtual;
  public
    property Value: T read GetValue write SetValue;
  end;

  TDateTimeField = TField<TDateTime>;

  TTrimmingStringField = class(TField<string>);
  protected
    function SetValue(const AValue: string); override;
  end;

  // Now use these fields as you'd expect

implementation

{ TField<T> }

function TField<T>.GetValue: T;
begin
  Result := FValue;
end;

procedure TField<T>.SetValue(const AValue: T);
begin
  FValue := AValue;
end;

{ TTrimmingStringField }

procedure TTrimmingStringField.SetValue(const AValue: string);
begin
  inherited SetValue(Trim(AValue));
end;
类型
t字段=类
私有的
f值:T;
受保护的
函数GetValue:T;事实上的
程序设定值(常数:T);事实上的
公众的
属性值:T读取GetValue写入SetValue;
结束;
TDateTimeField=TField;
TTrimmingStringField=类(TField);
受保护的
函数设置值(常量值:字符串);推翻
结束;
//现在,按照预期使用这些字段
实施
{TField}
函数TField.GetValue:T;
开始
结果:=fv值;
结束;
程序TField.SetValue(常数:T);
开始
f值:=有效值;
结束;
{TTrimmingStringField}
过程TTrimmingStringField.SetValue(常量值:字符串);
开始
继承的设置值(修剪(AValue));
结束;

多态性用于方法而不是属性。您可以定义具有虚拟getter和setter方法的属性,然后在这些getter和setter方法上使用多态性

但是,不允许多态方法更改参数的类型或返回值。为什么不呢

那么,假设我们编写以下代码:

type
  TMyBaseClass = class
  public
    procedure Foo(Arg: string); virtual;
  end;

....

var
  obj: TMyBaseClass;

....

obj.Foo('bar');
多态性的一个原则是,
obj
可以是
TMyBaseClass
的实例,也可以是从
TMyBaseClass
派生的任何类。编译器不需要知道
obj
的实际类型,只需要知道它是从
TMyBaseClass
派生的。该类型仅在运行时已知,多态虚拟方法调度确保调用派生方法

这意味着,
Foo
接受除首次引入虚方法时声明的参数以外的任何类型的参数是没有意义的


你需要的是一种叫做变体类型的东西。变体类型可以表示许多不同的类型。例如,任何合适的变量类型都能够表示整数、浮点值、字符串、日期和时间等。在Delphi中,您可以使用类似COM
Variant
类型或最近添加的
TValue
之类的内容。在Free Pascal中,我不太确定主要的变量类型是什么。

另外,OP可能会将多态性与泛型类型混淆。@DavidHeffernan我已修改代码,以使用字符串,正如您在上面看到的那样。但是,与字符串相比,变体可能会提高性能,您认为呢?我不太喜欢将日期存储为文本。转换时的错误范围。我更关心的是正确性而不是性能。我不能对性能发表评论,因为我不知道您的性能限制是什么。好吧,如果只是因为我从未使用过它,该变体将有机会。可能很有趣。使用字符串进行操作速度足够快,而且可能变体速度更快。变体基本上与FPC一起工作,与往常一样,当前版本的FPC可能比旧版本的Delphi更兼容。(与D7一样)afaik唯一已知的问题是,使用item[]数组的动态调度并不总是100%的。您能否解释一下泛型有多大帮助。“我必须承认,我看不出这是怎么回事。”大卫·费弗南我将我的答案扩大了一点,以解决这个问题。试着实现它。尝试编写一个可以返回整数或字符串的泛型方法。您几乎肯定会使用一个变体类型,很可能是TValue。@DavidHeffernan我建议的是非常基本的。添加了代码,这样你就能明白我的意思了。前提是多态性,尽管这样说,但不是这里的实际目标。@Rimfire我不确定这是否是一个问题。只要类是用Delphi代码声明的,比如上面的
TDateTimeField
,它就很可能看起来像PascalScript中的普通类。这正是我所说的“隐藏依赖泛型的事实”的意思;编译器将实例化/扩展它们以形成所需的普通类。
type
  TMyBaseClass = class
  public
    procedure Foo(Arg: string); virtual;
  end;

....

var
  obj: TMyBaseClass;

....

obj.Foo('bar');