多态性和属性

Polymorphism and properties

我正在尝试使用多态性并创建一个 属性 应该 return 不同的类型,具体取决于它是什么类型的对象。

为什么下面的代码会有这个编译器警告:

[警告:使用抽象方法 "SetValue"]

构建 class "TTimeField"

更重要的是,我怎样才能使它工作以便在创建 TTimeField 时可以为 Value 分配 TDateTime?事实上,有一条关于 "type mismatch".

的消息
  TBaseField = class
  private
    function  GetValue: string; virtual; abstract;
    procedure SetValue(AValue: string); virtual; abstract;
  public
    property  Value: string read GetValue write SetValue;
  end;

  { TTimeField }

  TTimeField = class(TBaseField)
  private
    FValue :  TDateTime;
    function  GetValue: TDateTime; override;
    procedure SetValue(AValue: TDateTime); override;
  public
    property  Value: TDateTime read GetValue write SetValue;
  end;

实施

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 并不能真正帮助您。子类不使用它的任何方法,也不提供任何额外的功能。

多态性是关于给定接口的不同实现(如在方法签名中),而调用者不需要知道这一点。 查看您的示例,可能只是您不一定要寻找多态性,而是寻找一种方法来实现类似 methods/properties 的各种数据类型,代码越少越好。这就是泛型通常被证明更有用的地方。

假设所有字段 类 需要存储自己类型的值,应该可以设置和检索。然后,您可以定义一个通用的 TField<T>,其中 T 是一个 "placeholder",用于字段应使用的数据类型。您可以给它一个 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;

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

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

好吧,假设我们写这段代码:

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

....

var
  obj: TMyBaseClass;

....

obj.Foo('bar');

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

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

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