Delphi 类:属性 与 Get/Set 方法

Delphi Classes: Property vs Get/Set methods

所以我对 OO 编程有点陌生。 Delphi 具有 属性 getters/ 相比,它旨在成为一种 "more elegant" 访问 class 数据的方式setters(在这里阅读usage of property vs getters/setters in business classes)。

什么时候应该直接使用字段,什么时候应该在属性中使用getters/setters?我只是在需要操作数据时才会猜测,但我不确定。

编辑:

省略除了 return 字段本身的值什么都不做的 setter 是错误的吗?

  property Field :integer read FField write FField;

为什么选择属性?

对于初学者,documentation on properties 的快速摘要:

A property, like a field, defines an attribute of an object. But while a field is merely a storage location whose contents can be examined and changed, a property associates specific actions with reading or modifying its data. Properties provide control over access to an object's attributes, and they allow attributes to be computed.

为什么不只有 setter 和 getter?

存储和访问的分离确实可以通过使用 getters 和 setters 来实现,而留下 属性 。的确如此,但是您 link 的问题源于语言差异: Delphi 确实具有属性,并且那里的答案已经解释了为什么要使用它们。两个最明显的原因是 (1) 更简洁的代码和 (2) 赋值能力。我认为 this answer 已经相当广泛地处理了它。

此外,在不使用属性的情况下,总是需要 getters 和 setters,而使用属性则不需要。假设一个setter实现,但是没有getter:一个属性可以直接读取字段。

Class完成

当你只声明一个属性的名字和它的类型时,Delphi的class完成默认读取一个私有字段和一个私有setter设置私有字段。请注意,这只是 default 配置,您可以再次根据需要进行修改。当您完全指定 属性 声明时,class 完成将遵守并添加一个私有字段,一个 getter and/or setter 作为您声明的要求。

没有 getter 和 setter

的属性

Is it wrong to omit a setter that does nothing but return the value of the field itself?

当 属性 没有 getter 也没有 setter 并且它只是读取和写入字段时,那么您可以得出结论,除了一致之外没有区别。但事实并非如此。该字段和 属性 具有不同的名称,因此可能具有不同的含义。意思是你可以给予。参见 Using properties instead of fields in class methods of the same unit is a bad practice?

何时使用 getter 或 setter?

... I'm gessing only when the data needs to be manipulated ...

好吧,这部分是正确的。操纵是众多原因之一。考虑类型 StringPrice 属性 及其私有字段 FPrice:

  • 限制:当价格需要等于或高于零时,
  • 委派:当FPrice是另一个领域的一部分时,或者当它超出class、
  • 的责任范围时
  • 验证:当价格逗号后面可能只有两位小数时,
  • 解释:当价格以千为单位输入,但应以分存储时,
  • 影响:当价格对另一个领域产生影响时,例如利率或保证金,
  • 激活:当编辑价格需要立即采取行动时,例如更新 GUI,
  • 转换:当价格以美元输入但应以日元存储时,
  • 取消:当价格没有意义时,例如以科学计数法输入时。

请注意 Price 属性 非常简陋。很可能将其 setter 或 getter 留作将来实施。但是想象一下更高级的属性,如果没有 setter 或 getter:

  • 咨询前需要创建的字段:

    function TMyObject.GetBarelyUsed: TRare;
    begin
      if FBarelyUsed = nil then
        FBarelyUsed := TRare.Create(Self);
      Result := FBarelyUsed;
    end;
    
  • 可以选择一个item,但是item本身不知道要做什么。取而代之的是所有者。注意在这种情况下完全没有私有字段:

    procedure TItem.SetSelected(Value: Boolean);
    begin
      if Value <> Selected then
      begin
        if Value then
          Owner.Selection.Add(Self)
        else
          Owner.Selection.Remove(Self);
      end;
    end;
    
  • 一个图像控件,专门用于查看您自己的图像格式。 FileName 属性 的赋值包括:检查正确的文件扩展名、检查文件是否存在、将文件名存储在私有字段中、加载文件、调整图片尺寸,或者撤销之前的赋值:

    procedure TAwDxfImage.SetFileName(const Value: TFileName);
    begin
      if FFileName <> Value then
        if SameText(ExtractFileExt(Value), '.' + SDxf) and
          FileExists(Value) then
        begin
          FFileName := Value;
          FGraphic.LoadFromFile(FFileName);
          FGraphic.SetBounds(Width, Height);
        end
        else
        begin
          FFileName := '';
          FGraphic.Clear;
        end;
    end;
    

Source: NLDelphi

不能接受的是这样做

TMyClass = class
private
public
  Fubar :integer;
end;

您的其余示例都很好。我很乐意收到您的代码,内容如下

TMyClass = class
private 
  FFu : integer;
public
  property Fu :integer read FFu write FFu;
end;

因为我可以放心的改成

TMyClass = class
private 
  FFu : integer;
  procedure SetFu(Value : Integer);
  function GetBar() : string;
public
  property Fu :integer read FFu write SetFu;
  property Bar : String read GetBar;
end;

不破坏现有代码。

我个人不喜欢什么都不做的setter

procedure TMyClass.SetFu(Value : Integer);
begin
  FFu := Value;
end;

但实际上它是无害的。

有帮助吗?

这将是使用 setter

的正当理由或动机
procedure TMyClass.SetFu(Value : Integer);
begin
  if FFu <> Value then begin  
    FFu := Value;
    if Assigned(FAfterFooChanged) then
      FAfterFooChanged(FFu);
  end;
end;

不是'manipulation'这样的...

除了 @NGLN 答案之外还有 属性 getter/setter 的另一个用例。

通过接口访问 class 实例只能通过实例方法。如果在这种情况下必须访问 属性,则必须实施 getter/setter 方法。

type
  IField = interface
    function GetField: integer;
    procedure SetField(value: integer);
    property Field: integer read GetField write SetField;
  end;

  TField = class(TInterfacedObject, IField)
  protected
    FField: integer;
    function GetField: integer;
    procedure SetField(value: integer);
  public
    property Field: integer read GetField write SetField;
  end;

var
  f: IField;
  x, n: integer;
...
  f := TField.Create;
  f.Field := 5;
  f.SetField(6);
  n := f.Field;
  x := f.GetField;

当然,根据您是否只需要对该 属性 的读取或写入访问权限,您可以在接口声明中省略 setter 或 getter。

请记住,通过接口访问实例会提供所有接口实现的方法 public 可见性。这就是为什么在上面的例子中你可以调用 f.GetField 尽管它被声明为受保护的(甚至是私有的)。