使用getter和setter(只获取和设置)而不是简单地为这些变量使用公共字段有什么好处?

如果getter和setter所做的不仅仅是简单的get/set,我可以很快地解决这个问题,但我不是100%清楚如何做到:

public String foo;

比:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }

而前者需要的样板代码要少得多。


当前回答

我能想到一个原因,为什么你不希望一切都公开。

例如,您从未打算在类外部使用的变量可以被访问,甚至可以通过链变量访问(即object.item.origin.x)直接访问。

通过将所有内容都设为私有,并且仅将您想要扩展的内容以及可能在子类中引用的内容设为受保护的,并且通常只将静态最终对象设为公共,那么您就可以通过使用setter和getter访问您想要的程序内容来控制其他程序员和程序可以在API中使用什么,以及它可以访问什么,以及不能访问什么,或者可能是其他恰好使用您的代码的程序员,可以在您的程序中进行修改。

其他回答

此外,这是为了“未来证明”你的课程。特别是,从字段更改为属性是ABI中断,因此如果您稍后决定需要更多的逻辑而不仅仅是“设置/获取字段”,那么您需要中断ABI,这当然会给针对您的类编译的任何其他内容带来问题。

因为从现在起2周(月、年),当您意识到setter需要做的不仅仅是设置值时,您还将意识到该属性已直接用于238个其他类:-)

我知道现在有点晚了,但我想有些人对表演感兴趣。

我做了一个性能测试。我写了一个类“NumberHolder”,它包含一个整数。您可以使用getter方法读取该IntegeranInstance.getNumber()或通过使用anInstance.number直接访问数字。我的程序通过两种方式读取数字1000000000次。该过程重复五次,并打印时间。我得到了以下结果:

Time 1: 953ms, Time 2: 741ms
Time 1: 655ms, Time 2: 743ms
Time 1: 656ms, Time 2: 634ms
Time 1: 637ms, Time 2: 629ms
Time 1: 633ms, Time 2: 625ms

(时间1是直接方式,时间2是吸气方式)

你看,吸气器(几乎)总是快一点。然后我尝试了不同次数的循环。我用了1000万和10万,而不是100万。结果:

1000万次循环:

Time 1: 6382ms, Time 2: 6351ms
Time 1: 6363ms, Time 2: 6351ms
Time 1: 6350ms, Time 2: 6363ms
Time 1: 6353ms, Time 2: 6357ms
Time 1: 6348ms, Time 2: 6354ms

1000万次循环,时间几乎相同。以下是10万(10万)个循环:

Time 1: 77ms, Time 2: 73ms
Time 1: 94ms, Time 2: 65ms
Time 1: 67ms, Time 2: 63ms
Time 1: 65ms, Time 2: 65ms
Time 1: 66ms, Time 2: 63ms

同样,对于不同数量的循环,getter比常规方法快一点。我希望这对你有所帮助。

公共字段并不比getter/setter对差,它除了返回字段并赋值之外什么都不做。任何差异都必须存在于其他因素,如可维护性或可读性。

getter/setter对的一个经常提到的优点不是。有一种说法是,您可以更改实现,而不必重新编译客户端。据推测,setter允许您稍后添加验证之类的功能,而您的客户甚至不需要知道它。然而,将验证添加到setter是对其前提条件的更改,违反了以前的合同,这很简单,“您可以将任何东西放在这里,稍后您可以从getter那里获得相同的东西”。

因此,现在您违反了合同,更改代码库中的每个文件是您应该做的事情,而不是避免。如果你避免这样做,你就假设所有的代码都假设这些方法的契约是不同的。

如果这不应该是约定,那么接口允许客户端将对象置于无效状态。这与封装正好相反。如果该字段从一开始就不能真正设置为任何值,为什么验证从一开始不存在?

同样的论点也适用于这些传递getter/setter对的其他假定优点:如果您稍后决定更改设置的值,那么您就违反了合同。如果您在派生类中重写默认功能,而不是进行一些无害的修改(如日志记录或其他不可观察的行为),那么您就违反了基类的约定。这违反了Liskov替代原则,这被视为OO的原则之一。

如果一个类对每个字段都有这些愚蠢的getter和setter,那么它就是一个没有不变量、没有契约的类。这真的是面向对象的设计吗?如果类只有那些getter和setter,那么它只是一个哑数据持有者,哑数据持有者应该看起来像哑数据持有者:

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};

向此类类添加传递getter/setter对不会增加值。其他类应该提供有意义的操作,而不仅仅是字段已经提供的操作。这就是如何定义和维护有用的不变量。

客户:“我可以用这个类的对象做什么?”设计器:“你可以读写几个变量。”客户:“哦……我想很酷吧?”

使用getter和setter是有原因的,但如果这些原因不存在,那么以虚假封装之神的名义制作getter/setter对并不是一件好事。使用getter或setter的有效原因包括经常提到的稍后可以进行的潜在更改,如验证或不同的内部表示。或者,该值应该是客户端可读但不可写的(例如,读取字典的大小),因此简单的getter是一个不错的选择。但是,当你做出选择时,这些理由应该存在,而不仅仅是你以后可能想要的潜在原因。这是YAGNI(你不需要它)的一个例子。

原因有很多。我最喜欢的是当你需要改变行为或调整你可以设置的变量时。例如,假设您有一个setSpeed(intspeed)方法。但你希望你只能将最大速度设置为100。您可以执行以下操作:

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}

现在,如果代码中的每个地方都使用公共字段,然后意识到需要上述要求,该怎么办?寻找公共领域的每一种用法,而不仅仅是修改你的setter。

我的2美分:)