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 ...
好吧,这部分是正确的。操纵是众多原因之一。考虑类型 String
的 Price
属性 及其私有字段 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;
不能接受的是这样做
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
尽管它被声明为受保护的(甚至是私有的)。
所以我对 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 ...
好吧,这部分是正确的。操纵是众多原因之一。考虑类型 String
的 Price
属性 及其私有字段 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;
不能接受的是这样做
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
尽管它被声明为受保护的(甚至是私有的)。