Getters/Setters. Evil. Period.

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

有一个古老的辩论,始于2003年Allen Holub在这篇著名文章为什么getter和setter方法是邪恶的中发起的,关于getter和setter是否是一种反模式,应该被避免使用,还是它是面向对象编程中我们不可避免需要的。我将尝试在这个讨论中加入我的见解。

以下文本的要点是:getter和setter是一种可怕的做法,使用它的人不能被原谅。再次强调,我并不是说应该尽可能避免使用get/set。不。我是说你的代码中绝对不能有它们。

足够傲慢以吸引你的注意了吗?你已经使用那个get/set模式15年了,而且你是一位受人尊敬的Java架构师?你不想听一个陌生人说这种胡言乱语?嗯,我理解你的感受。当我偶然看到David West的Object Thinking时,我几乎有同样的感觉,这是我到目前为止读过的关于面向对象编程的最好的书。所以,请冷静下来,试着理解我尝试解释的内容。

在面向对象的世界中,对于“访问器”(另一个称为获取器和设置器的名称),存在一些争议。我认为,所有这些争议都不足以成立。让我们简要地介绍一下这些争议。

告诉而不是询问 Allen Holub表示:“不要询问你需要完成工作所需的信息;而是询问拥有这些信息的对象来为你完成工作。”

违反封装原则 对象可以被其他对象撕裂,因为它们可以通过设置器注入任何新数据。由于任何人都可以更改它,因此对象无法安全地封装自己的状态。

暴露实现细节 如果我们可以从一个对象中获取另一个对象,那么我们过于依赖第一个对象的实现细节。如果明天它改变了,比如说结果的类型,我们也必须更改我们的代码。

所有这些辩解都是合理的,但它们忽略了主要观点。

大多数程序员认为对象是具有方法的数据结构。我引用了Bozhidar Bozhanov的文章《Getters and Setters Are Not Evil》(http://java.dzone.com/articles/getters-and-setters-are-not)中的一句话。

这个误解是一个巨大的误解的结果!对象不是”简单的数据持有者”。对象不是带有附加方法的数据结构。这个”数据持有者”的概念来自过程式语言,尤其是C和COBOL。我再说一遍:对象不是一组数据元素和操作它们的函数。对象不是一个数据实体。

A Ball and A Dog

在真正的面向对象编程中,对象就像你和我一样是活生生的存在。它们是有生命的有机体,拥有自己的行为、属性和生命周期。

一个活生生的有机体能拥有一个设置器吗?你能把一个球“设置”给一只狗吗?实际上并不能。但下面这段软件正是在做这样的事情:

这听起来怎么样?

你能从狗那里得到一个球吗?如果它吞下了球并且你正在进行手术,那么很可能可以。在那种情况下,是的,我们能够从狗那里“得到”一个球。这就是我所说的内容:

或者更荒谬的例子:

你能想象这个交易在现实世界中吗?

它看起来是否类似于你每天所写的内容?如果是,那么你是一个过程性程序员。承认吧。这是大卫·韦斯特在他的书的第30页上对此的看法。

你需要脑叶切除术吗?嗯,我绝对需要,并在阅读West的《Object Thinking》时得到了它。

开始像一个对象一样思考,你会立即给那些方法重新命名。这是你可能会得到的:

现在,我们将狗当作一只真正的动物,它可以从我们手中接过球,并在我们要求时将球归还给我们。值得一提的是,狗无法归还“NULL”。狗根本不知道什么是“NULL”。对象思维立即将NULL引用从你的代码中消除。

除此之外,对象思维将导致对象的不可变性,就像“狗的重量”这个例子一样。你应该这样重新编写它:

狗是一个不可变的生物有机体,不允许任何外部人改变她的体重、大小、名字等。她可以根据要求告诉你她的体重或名字。展示对象内部的特定”内部”请求的公共方法没有任何问题。但这些方法不是”获取器”,并且永远不应该带有”get”前缀。我们并不是从狗那里”获取”任何东西。我们并没有获取她的名字。我们只是询问她告诉我们她的名字。看到区别了吗?

这里我们并不是在讨论语义。我们是在区分过程式编程思维和面向对象编程思维。在过程式编程中,我们处理数据,根据需要进行操作、获取、设置和删除。我们掌控一切,而数据只是一个被动的组成部分。对我们来说,狗无关紧要—它只是一个”数据容器”。它没有自己的生活。我们可以自由地从中获取任何必要的东西,并设置任何数据。这就是C、COBOL、Pascal和许多其他过程式语言的工作方式。

相反,在真正的面向对象的世界中,我们将对象视为活生物,有自己的出生日期和死亡时刻—有自己的身份和习惯,如果你愿意的话。我们可以要求狗提供一些数据(例如,她的体重),她可能会将这些信息返回给我们。但我们始终记住,狗是一个主动组件。她决定在我们的请求之后会发生什么。

这就是为什么,在一个对象中以setget开头的任何方法在概念上都是不正确的。这与打破封装无关,正如许多人所争辩的那样。这取决于你是否像一个对象思考,或者你仍然在用Java语法写COBOL。

附:是的,你可以问,JavaBeans、JPA、JAXB和许多其他依赖于get/set符号的Java API怎么办?Ruby的内置功能(http://ruby-doc.org/docs/ruby-doc-bundle/UsersGuide/rg/accessors.html)简化了访问器的创建。嗯,这些都是我们的不幸。留在一个原始的过程式COBOL世界中要比真正理解和欣赏真正对象的美世界更容易。

补充:忘了说,是的,通过设置器进行的依赖注入也是一个可怕的反模式。关于这个问题,我会在接下来的帖子中详细说明。

再补充:这是我建议使用代替获取器的方法:打印机。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-28 at 14:38

sixnines availability badge   GitHub stars