使用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; }

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


当前回答

DataStructure和Object之间存在差异。

数据结构应该暴露其内部而不是行为。

一个物体不应该暴露其内部,但它应该暴露其行为,这也被称为德米特定律

大多数DTO被认为是一种数据结构,而不是对象。他们应该只公开自己的数据,而不是行为。在数据结构中设置Setter/Getter将暴露行为,而不是其中的数据。这进一步增加了违反德梅特定律的可能性。

鲍勃叔叔在他的《干净的代码》一书中解释了得墨忒耳定律。

有一种著名的启发式方法叫做得墨忒耳定律,它说:模块不应该知道它的对象的内部结构操纵。正如我们在上一节中看到的,对象隐藏其数据并暴露操作。这意味着对象不应公开其通过访问器的内部结构,因为这样做是为了暴露,而不是隐藏其内部结构。更准确地说,德米特定律说C类的方法f应仅调用以下方法:Cf创建的对象作为参数传递给f的对象保存在C的实例变量中的对象该方法不应在任何允许的函数返回的对象上调用方法。换句话说,与朋友交谈,而不是与陌生人交谈。

因此,根据这一点,LoD违规的例子是:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();

在这里,函数应该调用它的直接朋友的方法,这里是ctxt,它不应该调用它直接朋友的朋友的方法。但该规则不适用于数据结构。所以在这里,如果ctxt、option、scratchDir是数据结构,那么为什么要用一些行为包装它们的内部数据,并违反LoD。

相反,我们可以这样做。

final String outputDir = ctxt.options.scratchDir.absolutePath;

这满足了我们的需求,甚至没有违反LoD。

灵感来源于Robert C.Martin(Bob叔叔)的“清洁代码”

其他回答

在面向对象的语言中,方法及其访问修饰符声明该对象的接口。在构造函数、访问器和赋值器方法之间,开发人员可以控制对对象内部状态的访问。如果变量被简单地声明为公共的,那么就没有办法规范这种访问。当我们使用setter时,我们可以限制用户所需的输入。这意味着这个变量的输入将通过一个适当的通道,通道是我们预先定义的。所以使用setter更安全。

在以下情况下,应使用getter和setter:

您处理的是概念上属于属性的东西,但是:您的语言没有财产(或类似的机制,如Tcl的变量跟踪),或您的语言的属性支持不足以满足此用例,或者您的语言(或有时您的框架)惯用惯例鼓励此用例的getter或setter。

因此,这很少是一个通用的OO问题;这是一个特定于语言的问题,对于不同的语言(和不同的用例)有不同的答案。


从OO理论的角度来看,getter和setter是无用的。你的类的接口是它所做的,而不是它的状态。(如果不是,你写错了类。)在非常简单的情况下,类所做的只是,例如,表示直角坐标中的一个点,*属性是接口的一部分;getter和setter只是为这一点添油加醋。但在非常简单的情况下,属性、getter和setter都不是接口的一部分。

换一种说法:如果你认为你的类的消费者甚至不应该知道你有一个垃圾属性,更不用说随意更改它了,那么给他们一个set_spam方法是你最不想做的事情。

*即使对于这个简单的类,您也不一定希望设置x和y值。如果这真的是一个类,它不应该有translate、rotate等方法吗。?如果它只是一个类,因为您的语言没有记录/结构/命名元组,那么这实际上不是OO的问题…


但从来没有人做过一般的面向对象设计。他们用特定的语言进行设计和实现。在某些语言中,getter和setter远非一无是处。

如果您的语言没有财产,那么表示概念上是属性但实际上是计算或验证的东西的唯一方法是通过getter和setter。

即使你的语言确实有财产,也可能有不足或不合适的情况。例如,如果要允许子类控制属性的语义,在没有动态访问的语言中,子类不能用计算属性代替属性。

至于“如果我想稍后更改我的实现怎么办?”这个问题(在OP的问题和公认的答案中以不同的措辞重复了多次):如果它真的是一个纯实现更改,并且您从一个属性开始,您可以将其更改为一个属性而不影响接口。当然,除非你的语言不支持这一点。所以这真的又是同样的情况。

此外,遵循所使用语言(或框架)的习惯用法也很重要。如果你用C#编写了漂亮的Ruby风格的代码,那么除了你之外,任何有经验的C#开发人员都会很难读懂它,这很糟糕。有些语言的习俗文化比其他语言更为浓厚-Java和Python在习惯用法方面处于对立的两端,它们恰好拥有两种最强的文化,这可能不是巧合。

除了人类读者之外,还会有图书馆和工具要求你遵守惯例,如果你不遵守惯例,会让你的生活变得更艰难。将Interface Builder小部件挂接到ObjC财产以外的任何东西上,或者在没有getter的情况下使用某些Java模拟库,只会让您的生活更加困难。如果这些工具对你很重要,不要与它们对抗。

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

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

我做了一个性能测试。我写了一个类“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比常规方法快一点。我希望这对你有所帮助。

除非您当前的交付需要,否则不要使用getters setter。例如,如果要更改任何内容,请不要过多考虑将来会发生什么,这是大多数生产应用程序和系统中的更改请求。

思考简单、简单,必要时增加复杂性。

我不会仅仅因为我认为这是正确的或者我喜欢这种方法,就利用对拥有深厚技术知识的企业主的无知。

我编写了一个没有getters setter的大型系统,只使用了访问修饰符和一些验证n perform-biz逻辑的方法。如果您确实需要。使用任何东西。