【问题标题】:Why use property in a class?为什么在类中使用属​​性?
【发布时间】:2011-09-17 12:15:31
【问题描述】:

我只是想知道为什么我应该在类中使用属​​性而不是“普通”变量(类属性?)。我的意思是:

TSampleClass = class
  public
    SomeInfo: integer;
end;

TPropertyClass = class
  private
    fSomeInfo: integer;
  public
    property SomeInfo: integer read fSomeInfo write fSomeInfo;
end;

最大的不同是什么?我知道我可以分别定义获取或保存属性的 getter 和 setter 方法,但即使变量不是“属性”,这也是可能的。

我试图寻找为什么要使用它,但没有任何有用的东西出现,所以我在这里问。

谢谢

【问题讨论】:

  • Magicmaster - 我一定在这里遗漏了什么;您能解释一下如何为您的示例 TSampleClass.SomeInfo 定义 getter 和 setter,并在您为 SomeInfo 变量赋值或读取值时实际调用这些方法吗?
  • 看到这个问题:stackoverflow.com/q/3963874/267938

标签: delphi oop properties


【解决方案1】:

这只是特定案例的一个非常简单的示例,但它仍然是一个非常常见的案例。

如果您有可视控件,则可能需要在更改变量/属性时重新绘制控件。例如,假设您的控件有一个 BackgroundColor 变量/属性。

添加此类变量/属性的最简单方法是让它成为公共变量:

TMyControl = class(TCustomControl)
public
  BackgroundColor: TColor;
...
end;

TMyControl.Paint 过程中,您使用BackgroundColor 的值绘制背景。但这并没有。因为如果更改控件实例的BackgroundColor 变量,控件不会重新绘制自身。相反,新的背景颜色将在下次控件因其他原因重绘自身时才会使用。

所以你必须这样做:

TMyControl = class(TCustomControl)
private
  FBackgroundColor: TColor;
public
  function GetBackgroundColor: TColor;
  procedure SetBackgroundColor(NewColor: TColor);
...
end;

在哪里

function TMyControl.GetBackgroundColor: TColor;
begin
  result := FBackgroundColor;
end;

procedure TMyControl.SetBackgroundColor(NewColor: TColor);
begin
  if FBackgroundColor <> NewColor then
  begin
    FBackgroundColor := NewColor;
    Invalidate;
  end;
end;

然后使用控件的程序员必须使用MyControl1.GetBackgroundColor 来获取颜色,并使用MyControl1.SetBackgroundColor 来设置它。这很尴尬。

使用属性,您可以两全其美。确实,如果你这样做了

TMyControl = class(TCustomControl)
private
  FBackgroundColor: TColor;
  procedure SetBackgroundColor(NewColor: TColor);
published
  property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor;
end;

...

procedure TMyControl.SetBackgroundColor(NewColor: TColor);
begin
  if FBackgroundColor <> NewColor then
  begin
    FBackgroundColor := NewColor;
    Invalidate;
  end;
end;

然后

  • 从程序员的角度来看,他可以使用单个标识符、MyControl1.BackgroundColor 属性来读取和设置背景颜色,并且
  • 设置控件时会重新绘制控件!

【讨论】:

  • 我忘了提一件很重要的事情。其他一些答案已经涉及到它,但我想更详细地解释一下:您在对象检查器(OI)中看到的每个属性都是......一个属性!在上面的示例中,属性BackgroundColor 将列在 OI 中,因此您可以在设计时更改控件的外观。并且当您更改值时,即使在设计时,也会执行相应的代码以重绘控件!如果您没有使用属性,而只使用了GetBackgroundColor 函数和SetBackgroundColor 过程,那么OI 不会知道...
  • ... 该控件有一个名为BackgroundColor 的属性,并且它应该使用函数GetBackgroundColor 来读取它并使用过程SetBackgroundColor 来设置它。这是非常重要的一点,我相信你会感激的!事实上,您可能每天都在使用属性。例如,当您更改TButtonCaption 时,您正在使用TButton 实例的Caption 属性。这可能从字段变量中读取并具有 setter 函数,以便在您键入时重绘控件(实际上,Caption 属性不是string,而是TCaption ...
  • ... 由type TCaption = type string 定义。所以它是一个字符串,但又是一个独特的类型,对于这种类型,OI 知道它应该在每次更改 OI 中的值时使用 setter,而不仅仅是在您按 Return 时。但这是一个旁注。)
  • 令人筋疲力尽的答案。非常感谢您,以及其他回答者。
【解决方案2】:

有现实生活中的优势:

  • 属性可以轻松更改为读/写/read'n'write,无需在代码中使用单独的 Getter 和 Setter;
  • 只需在初始化部分添加一行,即可在子类中公开/发布属性;
  • 属性在设置字段时更加友好,将“Label.Font.SetSize(14)”与“Label.Font.Size := 14”进行比较,您可以将“:=”与制表符/空格和代码对齐将更具可读性;

编辑:我想到的另一件事是,属性迫使您将 Get/Set 方法限制为仅 1 个参数,这对 OOP 很有好处。将其与一些过度设计的功能进行比较:

GetItem(Index:integer; ForcedIndex:boolean=false):TItem //Forced index to get any value
GetItem(Index:integer; out Res:PItem):boolean //Result signals if out pointer is valid

【讨论】:

    【解决方案3】:

    我知道我可以定义 getter 和 setter 方法来分别获取或保存属性,但即使变量不是“属性”也是可能的。

    嗯,不。 Setter 和 getter 只是普通的方法,只有在它们用作属性的读取和写入成员时才会被调用。没有属性意味着没有 getter 或 setter,即使它们是这样命名的。 此外; setter 和 getter 通常被声明为私有或受保护的。因此,当您使用公共字段而不是使用公共属性时能够调用它们需要将这些方法移动到公共部分。

    此外,字段和属性之间的一个很大区别是可以发布,因此可以在对象检查器中使用。字段(除类或接口之外的其他类型)不能声明为已发布。

    属性在继承中也可能非常重要或有用。从技术上讲,您不能覆盖属性,但可以通过多种方式模拟覆盖。可以从 TDescendant 调用属性 Name 的一些示例,每个示例都有自己的用途:

    1) 抽象:

    TBase = class(TObject)
    protected
      function GetName: String; virtual; abstract;
      procedure SetName(const Value: String); virtual; abstract;
    public
      property Name: String read GetName write SetName;
    end;
    
    TDescendant = class(TBase)
    private
      FName: String;
    protected
      function GetName: String; override;
      procedure SetName(const Value: String); override;
    end;
    

    2a) 保护(就像 Krom 提到的,):

    TBase = class(TObject)
    private
      FName: String;
      function GetName: String;
      procedure SetName(const Value: String);
    protected
      property Name: String read GetName write SetName;
    end;
    
    TDescendant = class(TBase)
    public
      property Name;
    end;
    

    2b)

    TBase = class(TObject)
    private
      FName: String;
    protected
      function GetName: String;
      procedure SetName(const Value: String);
    end;
    
    TDescendant = class(TBase)
    public
      property Name: String read GetName write SetName;
    end;
    

    结合以上内容,您可以更改后代类的属性行为。

    【讨论】:

      【解决方案4】:

      将班级的“内部”与外界隔离开来是一种很好的编程习惯。此外,有关已发布属性的信息存储在为该类生成的 RTTI 中,并且可以通过其名称、枚举等方式访问。此功能用于例如从其序列化资源表单中读取表单时。

      【讨论】:

        【解决方案5】:

        使用属性的主要原因之一(不管它是否更 OO)是验证输入,例如,如果您需要将员工类别的年龄限制在 18..40 等有效范围内

          TEmp = class
          private
            FName: string;
            FAge: Integer;
            procedure SetAge(const Value: Integer);
            procedure SetName(const Value: string);
          published
            property Name:string read FName write SetName;
            property Age:Integer read FAge write SetAge;
          end;
        
        .....
        
        procedure TEmp.SetAge(const Value: Integer);
        begin
          if not (Value in [18..40]) then
            raise Exception.Create('Age must be between 18 and 40')
          else
            FAge := Value;
        end;
        

        【讨论】:

        • 在您的示例中,验证是您的 setter 的一部分,您不需要属性来拥有 setter,您可以将您的 setter 公开(仅当您确实需要发布您的 setter 时才有效)
        【解决方案6】:
        【解决方案7】:

        没有属性就无法监控变量的变化。

        您对属性的读/写不必是变量,它们可以是函数。然后你可以管理一个属性的“onChange”。

        例如

        TmyChange = procedure(Sender: Tobject) of object;
        
        
        private 
        Fchange : TmyChange;
        
        public
        property SomeInfo: integer read getFoo write setFoo;
        property onChange : TmyChange read Fchange write Fchange;
        
        function getFoo : integer
        begin
          return localFoo;
        end;
        
        function setFoo (value : integer)
        begin
          // validate incoming value
           localFoo=value;
          if assigned(Fchange) then Fchange(self);
        end;
        

        【讨论】:

        • 我发现了一个bug:在Delphi中,没有return声明。
        • 我发现了另一个错误:一个函数需要一个返回值。
        • 好吧,它的 getFoo = localfoo;.,鉴于我们在谈论一个类,这不是一个完全可编译的示例,只是一个演示..
        • @Smasher:每个数学家都是编译器。这就是为什么它们不太受欢迎...... ;)
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-12-20
        • 2011-07-20
        相关资源
        最近更新 更多