属性的动机是什么?

3

我有些困惑为什么编程语言会有属性。我是一名Java程序员,在职业生涯的开始阶段,自从我真正开始理解它以来,Java就是我唯一编写的语言。

在Java中,当然没有属性,我们编写getThis()和setThat(...)方法。

如果有属性,我们会得到什么好处呢?

谢谢。

编辑:另一个问题:具有属性的语言中会出现什么命名约定?


对于这个问题,有一个小小的补充(不是开新问题):为什么不直接使用public int age; 然后放弃所有属性的新语法。使用public int age,你仍然可以编写“自然”的person.age = 30。 我读过,这将允许您添加更复杂的逻辑(甚至连接到数据库)。 - OscarRyz
但我认为那不是放置业务逻辑(甚至所有逻辑)的正确位置,这就是为什么首先是属性的原因,不是吗? - OscarRyz
另一个评论。Java 7会引入属性吗?我有几个新功能无法实现。(例如闭包) - OscarRyz
https://dev59.com/zXVC5IYBdhLWcg3w1E7w - George Stocker
12个回答

9
你认为哪个更自然?
// A
person.setAge(25)
// B
person.age = 25;
// or
person.Age = 25; //depending on conventions, but that's beside the point

大多数人会选择B。

它不仅是语法糖,还有助于进行反射;您实际上可以区分数据和操作,而无需求助于方法名称。

以下是C#中的示例,供那些不熟悉属性的人参考:

class Person
{
    public int Age
    {
        set
        {
            if(value<0)
                throw new ArgumentOutOfRangeException();

            OnChanged();
            age = value;
        }

        get { return age; }
    }

    private int age;
    protected virtual void OnChanged() { // ... }
}

此外,大多数人总是使用属性而不是在以后将公共成员推广出去,原因与我们始终使用get/set相同;没有必要重写绑定到数据成员的旧客户端代码。


关于反射的部分是正确的。拥有一个反映对象数据的属性在语义上也更加重要。 - Bruno Lopes
@COincoin:为什么不只用公共成员?顺便说一句,应该是 person.age 而不是 person.Age。 - OscarRyz
1
@Oscar...不一定...大多数我看到的C#风格指南都使用"Pascal Casing"来命名属性,而不是"Camel Casing"。 - Beska
@Beska 这个问题是在 Java 标签下发布的,而不是 C# 标签下... Java 的命名约定是 age。 - TofuBeer
@Beska:没错,但这是Java。我的意思是,在C#中,方法名以大写字母开头。我的意思是,保持一致性会更好。 - OscarRyz
显示剩余4条评论

7
语法更加简洁易懂:
button.Location += delta;

比:

button.setLocation(button.getLocation() + delta);

6

编辑:

以下代码假定您手动完成所有操作。在我的示例世界中,编译器将生成简单的get/set方法,并将所有直接变量访问转换为这些方法。如果这样做不行,则客户端代码必须重新编译,这违背了其重要目的。

原文:

属性的主要论点是它消除了从变量到方法的转换需要重新编译代码的需求。

例如:

public class Foo
{
    public int bar;
}

如果我们稍后决定验证 "bar",我们需要执行以下操作:
public class Foo
{
    private int bar;

    public void setBar(final int val)
    {
        if(val <= 0)
        {
            throw new IllegalArgumentException("val must be > 0, was: " + val);
        }

        bar = val;
    }

    public int getBar()
    {
        return (bar);
    }
}

但是增加set/get方法将会破坏所有的代码。如果使用属性来完成,则可以在事后添加验证而不会破坏客户端代码。

我个人不喜欢这个想法-我更喜欢使用注释,并自动生成简单的set/get,能够根据需要提供自己的set/get实现(但我不喜欢隐藏的方法调用)。


只有属性属于字节码的一部分才能起作用,如果只是语法糖,仍然需要重新编译。例如C#属性就是纯语法,实际上创建了“get_prop”类型的方法。如果你在那里从字段更改为属性,仍然需要重新编译。 - DefLog
使用属性后,客户端代码仍然需要重新编译(只是不需要重写)吗? - 0yb4bs43
由于您提到了注解,我对@Get、@Set和@GetSet(... :)注解的获取感到兴奋。这意味着不需要额外的语法糖! - Esko
Esko - @Get / etc... 注解仍然不允许您传递属性。您最终需要传递字符串,这使得代码变得脆弱且重构非常容易出错。 - Kevin Day
想象一下,你声明了一个实例变量,比如"@readonly property int foo;"(property是新关键字)。这将使编译器生成一个getMethod(没有set),并且由于它有property关键字,它会将所有的.foo转换为对.getFoo()的调用。 - TofuBeer

3

两个原因:

  1. 更简洁的语法;以及
  2. 更清晰地向类的用户表明状态(属性)和行为(方法)之间的区别。

我不明白你的第二点 - 例如在C#中,属性里不能有任意代码吗? - 0yb4bs43
是的,但根据惯例你不这样做。请参考https://dev59.com/gnRB5IYBdhLWcg3wgXdV#601648 - cletus
@cletus:呵呵呵,这是一个相当有用但不建议使用的结构。尽管如此,如果它可用的话,它可能会很方便(和被滥用)。 - OscarRyz

2

在Java中,getter和setter本质上是属性。

在其他现代语言(如c#)中,它只是使语法更易于使用/理解。

它们是不必要的,在大多数情况下都有解决方法。

这实际上是个人偏好问题,但如果您使用的语言支持它们,我建议使用它们 :)


1

一开始我也很苦恼,但现在我真的很欣赏它们。在我看来,属性允许我以自然的方式与公开数据交互,同时不失 getter/setter 方法提供的封装性。换句话说,我可以将我的属性视为字段,但如果选择不这样做,实际字段就不会被真正暴露出来。在 C# 3.0 中使用自动属性更好,因为对于大多数字段——我想要允许消费者读/写数据的字段——我需要编写的代码更少:

public string Prop { get; set; }

在需要部分可见性的情况下,我可以轻松地限制我想要的访问器。
public string Prop { get; private set; }

所有这些都可以通过getter / setter方法完成,但术语更高,使用起来也不太自然。

1
面向对象编程的一个通用规则是永远不要更改现有接口。这确保了调用对象的对象虽然内部内容可能会发生变化,但不需要知道这一点。
其他语言中的属性是伪装成特定语言功能的方法。在Java中,属性仅通过约定来区分。虽然通常情况下这很有效,但也有限制的情况。例如,有时您需要使用hasSomething而不是isSomething或getSomething。
因此,它允许名称的灵活性,同时工具和其他依赖项的代码仍然可以区分它们。
此外,代码可以更紧凑,并且设计上将get和set组合在一起。

1
在面向对象软件构造2中,Bertrand Meyer将其称为“统一访问原则”,其基本思想是当一个属性从简单属性(例如:仅为整数)变成派生属性(函数调用)时,使用它的人不应该知道这一变化。
你不希望每个使用你的代码的人都必须从
int x = foo.y;
改为
int x = foo.y();
因为这会破坏封装性,因为你没有更改“接口”,只是更改了“实现”。

0

一切关乎绑定

曾经我认为属性只是语法糖(即通过减少开发人员的输入量来帮助他们)。但随着我越来越多地进行GUI开发并开始使用绑定框架(JGoodies、JSR295),我发现,语言级别的属性远远不止是语法糖。

在绑定场景中,您基本上定义了规则,告诉系统“对象A的属性X应始终等于对象B的属性Y”。简写为:A.x <-> B.y

现在,想象一下您将如何在Java中编写绑定库。目前,无法直接引用 'x' 或 'y' 作为语言原语。你只能将它们作为字符串引用(并通过反射访问它们)。实际上,A."x" <-> B."y"

这在重构代码时会造成巨大的问题。

还有其他考虑因素,包括属性更改通知的正确实现。如果您查看我的代码,则每个setter都需要至少3行才能执行非常简单的操作。此外,其中的三行之一还包括另一个字符串:

public void setFoo(Foo foo){
  Foo old = getFoo();
  this.foo = foo;
  changeSupport.firePropertyChange("foo", old, foo);
}

所有这些浮动的字符串都是一个完全的噩梦。

现在,想象一下如果属性是语言中的一等公民。这开始提供几乎无限的可能性(例如,想象一下直接向属性注册侦听器,而不必处理PropertyChangeSupport及其必须添加到每个类中的3个神秘方法)。想象一下能够将属性本身(而不是属性值,而是Property对象)传递到绑定框架中。

对于Web层开发人员,想象一下一个Web框架可以从属性名称自己构建表单ID值(类似于registerFormProperties(myObject.firstname, myObject.lastname, someOtherObject.amount)),以允许在将表单提交回服务器时进行对象属性值的往返填充。现在要做到这一点,您必须传递字符串,并且重构变得非常麻烦(一旦您依赖字符串和反射来连接事物,重构实际上变得非常可怕)。

所以,对于那些通过绑定处理动态数据更新的人来说,属性是语言中非常需要的功能 - 远远不止是语法糖。


0

您还可以创建派生字段和只读/只写字段。在我所使用的语言中,大多数属性不仅允许您分配简单字段,还允许您将完整函数分配给属性。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接