题 为什么要使用getter和setter / accessors? [关闭]


使用getter和setter的优点是什么 - 只能获取和设置 - 而不是简单地为这些变量使用公共字段?

如果getter和setter做的不仅仅是简单的获取/设置,我可以非常快地解决这个问题,但我不是100%明确如何:

public String foo;

更糟糕的是:

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

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


1305
2017-10-14 18:20


起源


@Dean J:与许多其他问题重复: stackoverflow.com/search?q=getters+setters - Asaph
当然,当对象不需要更改属性时,两者都同样糟糕。我宁愿将所有内容都设为私有,然后添加getter(如果有用),以及setter(如果需要)。 - Tordek
谷歌“访问者是邪恶的” - OMG Ponies
如果您正在编写功能代码或不可变对象,那么“访问器就是邪恶的”。如果你碰巧在编写有状态的可变对象,那么它们就非常重要。 - Christian Hayter
告诉,不要问。 pragprog.com/articles/tell-dont-ask - Dave Jarvis


答案:


实际上有 很多好理由 考虑使用访问者 而不是直接暴露类的字段 - 超越封装的参数,使未来的更改更容易。

以下是我所知道的一些原因:

  • 与获取或设置属性相关联的行为的封装 - 这允许稍后更容易地添加附加功能(如验证)。
  • 隐藏属性的内部表示,同时使用替代表示公开属性。
  • 使公共接口与变更隔离 - 允许公共接口在实现更改时保持不变,而不会影响现有的使用者。
  • 控制属性的生命周期和内存管理(处理)语义 - 在非托管内存环境(如C ++或Objective-C)中尤为重要。
  • 为属性在运行时更改时提供调试拦截点 - 在某些语言中调试属性更改为特定值的时间和位置可能非常困难。
  • 改进了与旨在针对属性获取器/设置器进行操作的库的互操作性 - 可以想到模拟,序列化和WPF。
  • 允许继承者通过重写getter / setter方法来更改属性的行为和暴露的语义。
  • 允许getter / setter作为lambda表达式而不是值传递。
  • getter和setter可以允许不同的访问级别 - 例如get可以是公共的,但是set可以受到保护。

794
2017-10-14 18:47



+1。只是添加:允许延迟加载。允许写入时复制。 - NewbiZ
@bjarkef:我相信 public 访问器方法导致代码重复,并公开信息。编组(序列化)不需要公共访问器。在某些语言(Java)中, public get访问器不能在不破坏依赖类的情况下更改返回类型。而且,我认为它们与OO原则背道而驰。也可以看看: stackoverflow.com/a/1462424/59087 - Dave Jarvis
@sbi:即使在设计良好的框架中,使用属性设置器购买的一件事是能够在属性发生变化时让对象通知另一个对象。 - supercat
@supercat:但是,我通常不会推广公共数据。其他对象的通知也可以通过真实方法完成,这些方法在具有(或多或少)公共数据字段的光数据容器上提供显着的抽象。代替 plane.turnTo(dir); plane.setSpeed(spd); plane.setTargetAltitude(alt); plane.getBreaks().release(); 我想说 plane.takeOff(alt)。需要更改哪些内部数据字段才能将平面放入 takingOff 模式不是我的顾虑。还有什么其他对象(breaks)方法通知我也不想知道。 - sbi
@BenLee:我真的不知道怎么说。 “访问者”只是“getters / setters”的同义词,在我的前两篇评论中,我解释了为什么那些滥用“OOP”这个词。如果你需要直接进入它并直接操纵状态,那么它就不是该术语的OOP意义上的对象。 - sbi


因为从现在起2周(几个月,几年),当你意识到你的二传手需要做的时候 更多 不仅仅是设置值,你还会意识到该属性已被直接用于其他238个类:-)


389
2017-10-14 18:23



我坐着,盯着一个从未需要的500k线应用程序。也就是说,如果它需要一次,它就会开始造成维护噩梦。足够好的复选标记给我。 - Dean J
我只能羡慕你和你的应用程序:-)这就是说,它真的取决于你的软件堆栈。例如,Delphi(以及C# - 我认为?)允许您将属性定义为一等公民,他们可以在这里直接读/写一个字段但是 - 如果您需要它 - 也可以通过getter / setter方法来实现。穆乔方便。 Java,唉,没有 - 更不用说强制你使用getter / setter的javabeans标准了。 - ChssPly76
虽然这确实是使用访问器的一个很好的理由,但许多编程环境和编辑器现在都支持重构(在IDE中或作为免费插件),这在某种程度上减少了问题的影响。 - LBushkin
听起来像是成熟时的优化 - cmcginty
当你想知道是否实现getter和setter时,你需要问的问题是: 为什么一个类的用户需要访问该类的内部呢? 它们是直接执行还是由瘦伪层屏蔽并不重要 - 如果用户需要访问实现细节,那么这表明该类没有提供足够的抽象。也可以看看 这个评论。 - sbi


公共字段并不比除了返回字段并分配给它之外什么都不做的getter / setter对更差。首先,很明显(在大多数语言中)没有功能差异。任何差异必须与其他因素有关,例如可维护性或可读性。

经常提到的吸气剂/固定剂对的优点不是。有这样的说法,您可以更改实施,您的客户不必重新编译。据推测,setters允许您稍后添加验证等功能,而您的客户甚至不需要了解它。但是,向setter添加验证是对其前提条件的更改, 违反了以前的合同,很简单,“你可以把任何东西放在这里,你可以在以后从吸气器那里得到同样的东西”。

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

如果那不应该是合同,那么接口允许客户端将对象置于无效状态。 这与封装完全相反 如果该字段从一开始就无法真正设置为任何内容,为什么从一开始就没有验证?

同样的论证适用于这些传递的getter / setter对的其他假设优点:如果你以后决定改变设置的值,那么你就违反了合约。如果覆盖派生类中的默认功能,超出一些无害的修改(如日志记录或其他不可观察的行为),则会违反基类的约定。这违反了Liskov Substitutability Principle,这被视为OO的原则之一。

如果一个类为每个字段都有这些愚蠢的getter和setter,那么它就是一个没有任何不变量的类, 没有合约。这真的是面向对象的设计吗?如果所有课程都是那些吸气者和制定者,那么它只是一个愚蠢的数据持有者,而愚蠢的数据持有者应该看起来像愚蠢的数据持有者:

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

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

客户:“我能用这个类的对象做什么?”
设计师:“你可以读写几个变量。”
客户:“哦......很酷,我猜?”

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


307
2017-08-24 10:55



很棒的答案(+1)。我唯一的批评是,我花了好几读才弄清楚最后一段中的“验证”与前几段中的“验证”有何不同(后者在后一种情况下抛出,但在前者中推广);在这方面调整措辞可能会有所帮助。 - Lightness Races in Orbit
这是一个很好的答案,但是当前时代已经忘记了“信息隐藏”是什么或它是什么。他们从未读过关于不变性的内容,并且在追求最敏捷的时候,从来没有画过那个定义了对象的合法状态是什么而不是什么的状态转换图。 - Darrell Teague
一个关键的观察是,吸气剂比定位器更有用。 getter可以返回计算值或缓存的计算值。所有的setter都可以做一些验证而不是改变 private 领域。如果一个类没有setter,那么很容易让它变成不可变的。 - Raedwald
呃,我想你不知道类不变量是什么。如果它是一个非平凡的结构,它几乎意味着它具有不变的支持,并且如果没有这些,就无法设计它。 “有一个错误,一个成员更新错了。”也几乎读起来像“成员更新违反了类不变量”。设计良好的类不允许客户端代码违反其不变量。 - R. Martinho Fernandes
'哑数据持有者的+1应该看起来像愚蠢的数据持有者'不幸的是,现在每个人似乎都围绕着愚蠢的数据持有者设计一切,而且很多框架甚至需要它...... - Joeri Hendrickx


很多人都在谈论吸气者和制定者的优势,但我想扮演魔鬼的拥护者。现在我正在调试一个非常大的程序,程序员决定让所有程序都得到getter和setter。这看起来不错,但它是一个逆向工程的噩梦。

假设您正在查看数百行代码,并且您遇到了这样的问题:

person.name = "Joe";

这是一段非常简单的代码,直到你意识到它是一个二传手。现在,你跟着那个setter,发现它还设置了person.firstName,person.lastName,person.isHuman,person.hasReallyCommonFirstName,并调用了person.update(),它将查询发送到数据库等等。哦,那是你的内存泄漏发生的地方。

乍看之下理解本地代码是一个具有良好可读性的重要特性,即getter和setter往往会破坏。这就是为什么我尽可能地避免使用它们,并尽量减少使用它们时所做的事情。


75
2017-10-14 21:00



是。目前正在重构一个大型代码库,这一直是一场噩梦。 getter和setter做得太多了,包括调用其他非常繁忙的getter和setter,最终将可读性降低到零。验证是使用访问器的一个很好的理由,但使用它们可以做更多的事情似乎消除了任何潜在的好处。 - Fadecomic
这是反对语法糖的论据,而不是一般的反对者。 - Phil
真实而又真实,但是,它指出了为什么单个属性设置器为无效状态打开了大门,而绝对没有补救措施来确保允许抽象泄漏的任何给定对象的完整性。 - Darrell Teague
“这是一段非常简单的代码,直到你意识到它是一个二传手” - 嘿,是关于Java还是关于C#的原始帖子? :) - Honza Zidek
我完全同意可读性。它完全清楚发生了什么 person.name = "Joe"; 叫做。信息的方式没有任何缺点,语法高亮显示它是一个字段,重构更简单(无需重构两个方法和一个字段)。其次,所有那些浪费的脑循环思考“getIsValid()”或者它应该是“isValid()”等都可以被遗忘。我不能想到单一的时间我已经被一个getter / setter从bug中拯救出来。 - Will


原因很多。我最喜欢的是当你需要改变行为或规范你可以在变量上设置的内容时。例如,假设您有一个setSpeed(int speed)方法。但是你想要你只能设置100的最高速度。你会做的事情如下:

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

现在如果您的代码中使用公共字段,然后您意识到您需要上述要求,该怎么办?尽情享受公共领域的每一次使用,而不仅仅是修改你的二传手。

我2美分:)


43
2017-10-14 18:27



追捕公共领域的每一次使用都不应该那么难。将其设为私有,让编译器找到它们。 - Nathan Fellman
这当然是真的,但为什么要比它设计的更难。获取/设置方法仍然是更好的答案。 - Hardryv
@Nathan: 查找 其他用法不是问题。 更改 他们都是。 - Graeme Perrow
@GraemePerrow不得不改变它们是一个优势,而不是一个问题:(如果你的代码假定速度可能高于100(因为,你知道,在你违反合同之前,它可以!)(如果!while(speed < 200) { do_something(); accelerate(); }) - R. Martinho Fernandes
这是一个非常糟糕的例子!有人应该致电: myCar.setSpeed(157); 经过几行 speed = myCar.getSpeed(); 现在......我希望您在尝试理解原因时尽快调试 speed==100 什么时候应该 157 - Piotr Aleksander Chmielowski


在纯粹的面向对象的世界中,getter和setter是一个 可怕的反模式。阅读这篇文章: getter / setter方法。邪恶。期。简而言之,它们鼓励程序员将对象视为数据结构,这种思维方式纯粹是程序性的(如COBOL或C)。在面向对象的语言中,没有数据结构,只有暴露行为的对象(不是属性/属性!)

您可以在第3.5节中找到更多关于它们的信息 优雅的物体 (我的关于面向对象编程的书)。


43
2017-09-23 16:39



吸气者和制定者建议贫血的领域模型。 - Raedwald
有趣的观点。但在大多数编程环境中,我们需要的是数据结构。以链接文章的“狗”为例。是的,你不能通过设置一个属性来改变现实世界的狗的体重......但是 new Dog() 不是狗。保存关于狗的信息是对象。对于这种用法,能够纠正错误记录的重量是很自然的。 - Stephen C
@StephenC这正是程序语言教我们思考的方式。对象思维完全相反。对象没有 保持 关于狗的信息。物体 是一个 狗。我的这本书可能有所帮助: 优雅的物体 - yegor256
好吧,我告诉你了 最 有用的程序不需要建模/模拟真实世界的对象。 IMO,这根本不是编程语言。这是关于我们为其编写程序的内容。 - Stephen C
现实世界与否,叶戈尔是完全正确的。如果您拥有的是真正的“结构”,并且您不需要编写任何按名称引用它的代码,请将其放在哈希表或其他数据结构中。如果你确实需要为它编写代码,那么将它作为一个类的成员,并将操作该变量的代码放在同一个类中,并省略setter和getter。 PS。虽然我主要分享了叶戈尔的观点,但我开始相信没有代码的带注释的bean是有些有用的数据结构 - 有时也需要getter,setter不应该存在。 - Bill K


访问器和更改器的一个优点是您可以执行验证。

例如,如果 foo 是公开的,我可以轻松地设置它 null 然后其他人可以尝试在对象上调用方法。但它不再存在了!有了 setFoo 方法,我可以保证 foo 从未设定过 null

访问器和更改器也允许封装 - 如果您不应该在设置后看到值(可能是在构造函数中设置然后由方法使用,但从不应该更改),任何人都不会看到它。但是,如果您可以允许其他类查看或更改它,您可以提供适当的访问者和/或更改者。


36
2017-10-14 18:25





取决于您的语言。你已经标记了这个“面向对象”而不是“Java”,所以我想指出ChssPly76的答案是依赖于语言的。例如,在Python中,没有理由使用getter和setter。如果需要更改行为,可以使用属性,该属性围绕基本属性访问包装getter和setter。像这样的东西:

class Simple(object):
   def _get_value(self):
       return self._value -1

   def _set_value(self, new_value):
       self._value = new_value + 1

   def _del_value(self):
       self.old_values.append(self._value)
       del self._value

   value = property(_get_value, _set_value, _del_value)

28
2017-10-14 18:32



是的,我在回答下面的评论中也说了很多。 Java并不是唯一使用getter / setter作为拐杖的语言,就像Python不是唯一能够定义属性的语言一样。然而,重点仍然是 - “财产”不是“公共领域”。 - ChssPly76
@jcd - 完全没有。您通过公开您的公共字段来定义您的“界面”(公共API在这里将是一个更好的术语)。一旦完成,就不会再回头了。属性不是字段,因为它们为您提供了一种机制来拦截访问字段的尝试(如果定义了这些字段,则将它们路由到方法);然而,这只不过是getter / setter方法的语法糖。它非常方便,但它并没有改变底层范例 - 暴露无法控制访问它们的字段违反了封装原则。 - ChssPly76
@ ChssPly76-我不同意。我有同样多的控制权,就好像它们是属性一样,因为我可以随时随地使它们成为属性。使用样板getter和setter的属性与raw属性之间没有区别,除了raw属性更快,因为它使用底层语言,而不是调用方法。在功能上,它们是相同的。封装可能被违反的唯一方法是你是否认为括号(obj.set_attr('foo'))本质上优于等号(obj.attr = 'foo')。公共访问是公共访问。 - jcdyer
@jcdyer尽可能多地控制是,但没有那么多的可读性,其他人经常错误地认为 obj.attr = 'foo' 只设置变量而不发生任何其他事情 - Timo Huovinen
@TimoHuovinen与Java中的用户有什么不同之处 obj.setAttr('foo') “只是设置变量而不发生任何其他事情”?如果它是一个公共方法,那么它是一个公共方法。如果你使用它来实现一些副作用,并且它是公开的,那么你最好能够指望所有工作 好像只发生了那种预期的副作用 (与 所有 其他实现细节和其他副作用,资源使用,等等,隐藏在用户的关注之下)。这与Python完全没有区别。 Python的语法实现效果更简单。 - ely


好吧,我只想补充一点,即使有时它们对于变量/对象的封装和安全性是必要的,如果我们想要编写一个真正的面向对象程序,那么我们需要 停止过度使用配件因为有时候我们非常依赖它们,而不是真的有必要,而且几乎和我们把变量公之于众一样。


23
2017-08-22 14:47