为什么要使用getter和setter/accessors?

Dea*_*n J 1472 java oop getter setter abstraction

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

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

public String foo;
Run Code Online (Sandbox Code Playgroud)

更糟糕的是:

private String foo;
public void setFoo(String foo) { this.foo = foo; }
public String getFoo() { return foo; }
Run Code Online (Sandbox Code Playgroud)

而前者需要很少的样板代码.

LBu*_*kin 927

实际上有许多充分的理由考虑使用访问器而不是直接暴露类的字段 - 除了封装的参数和使未来的更改更容易.

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

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

  • +1.只是添加:允许延迟加载.允许写入时复制. (54认同)
  • @supercat:但是,我通常不会推广公共数据.其他对象的通知也可以通过真实方法完成,这些方法在具有(或多或少)公共数据字段的光数据容器上提供显着的抽象.而不是'plane.turnTo(dir); plane.setSpeed(SPD); plane.setTargetAltitude(ALT); plane.getBreaks().release();`我想说'plane.takeOff(alt)`.为了将飞机置于"takeOff"模式,需要更改哪些内部数据字段不是我的担忧.还有什么其他对象(`break`)方法通知我也不想知道. (20认同)
  • @sbi:即使在设计良好的框架中,使用属性设置器购买的一件事是能够在属性更改时让对象通知另一个对象. (15认同)
  • -1 因为点号。1 是开发陷阱#1:经典的 Getters/Setter 不封装行为。因为他们不表达行为。Cutomer::setContractEndDate() / Customer::getContractEndDate() 不是任何行为,它根本不封装任何内容。这是伪造的封装。Customer::cancelContract() 是一种行为,您实际上可以在不更改界面的情况下修改逻辑。如果您通过 getter 和 setter 公开原子属性,它将不起作用。 (13认同)
  • @BenLee:我真的不知道怎么说."访问者"只是"getters/setters"的同义词,在我的前两篇评论中,我解释了为什么那些滥用"OOP"这个词.如果你需要直接进入它并直接操纵状态,那么它就不是该术语的OOP意义上的对象. (7认同)
  • 穿着凯夫拉防暴服、佩戴防毒面具和盾牌走出去有很多充分的理由。你会更安全!您将免受化学和生物威胁。还有很多!因此,每种只考虑积极因素或只考虑消极因素的方法都是不平衡的,因此在实践中会导致损失。因为“好东西”是要付出代价的,而且这个代价是你不能忽视的。getter 和 setter 的价格是多少?你的时间、你的注意力、你的生活。因此,只要您和其他人在创建和维护代码所花费的时间方面获得更多好处,就可以使用它们。 (7认同)
  • @bjarkef:我相信`public`访问器方法会导致代码重复,并暴露信息.编组(序列化)不需要公共访问器.在某些语言(Java)中,`public` get访问器不能在不破坏依赖类的情况下更改返回类型.而且,我认为它们与OO原则背道而驰.另见:http://stackoverflow.com/a/1462424/59087 (5认同)
  • 1.不可预见的副作用,当有人看到 getter 或 setter 时,他们不会采取额外的行为。不要对你的程序员同事撒谎。2. 你不应该公开状态,只需围绕行为设计你的界面 3. 可能违反里氏替换原则。另外,设计行为不提供对状态的访问。4. 我不明白 getter/setter 如何实现这一点 5. 不要以调试为唯一目的编写代码。特别是在您的类中,它应该是您域的核心。 (3认同)
  • 6. 这样的库使用起来是一场噩梦,你应该为它们提供一个 DTO,这里没有好的方法,但最不坏的是反射(请不要使用它编写任何东西) 7. 再次强调,不要激怒违反里氏替换原则 8. 只是看不出有什么意义。如果对物体状态进行不受控制的外部修改是一个可怕的想法,那么在空中发射 lambda 似乎更糟糕。9.唯一半有效的理由。但只有在使用继承时才有效,这目前被认为是反模式。 (3认同)
  • @BenLee:不知道Ruby,你的解释让我有些困惑,但这听起来像是制度化的getter/setter. (2认同)
  • 有趣的是,没有人对此说了什么......但是在调试,跟踪代码时,getter setter可能很有用.每次读取/写入/删除公共属性时都不可能轻松跟踪...但是使用访问器/属性,您只需在所需功能或断点内添加跟踪即可. (2认同)
  • 虽然我个人使用getter和setter,但所有这些使用带有私有字段的getter/setter的要点都属于YAGNI类别,只是说... (2认同)
  • @andreagalle 造成这种情况的原因有很多。也许你有一个无法从外部改变的属性。在这种情况下,您也可以完全删除 setter。但也许您想在子类中更改这种更改阻止行为,然后可以利用“protected”方法。 (2认同)

Chs*_*y76 448

由于2周(月,年)从现在开始,当你意识到你的二传手需要做更多的比刚才设置的值,你也意识到,财产已经在其他238类直接使用:-)

  • 我坐着,盯着一个从未需要的500k线应用程序.也就是说,如果它需要一次,它就会开始造成维护噩梦.足够好的复选标记给我. (80认同)
  • 听起来像是成熟前的优化 (57认同)
  • 当你想知道是否要实现getter和setter时,你需要问的问题是:_为什么类的用户需要访问类的内部呢?_无论是直接执行还是屏蔽都不重要伪层 - 如果用户需要访问实现细节,那么这表明该类没有提供足够的抽象.另请参阅[此评论](http://stackoverflow.com/questions/1568091/why-use-getters-and-setters#comment16169586_1568230). (40认同)
  • 我只能羡慕你和你的应用程序:-)这就是说,它还取决于你的软件堆栈.例如,Delphi(以及C# - 我认为?)允许您将属性定义为一等公民,他们可以在这里直接读/写一个字段但是 - 如果您需要它 - 也可以通过getter/setter方法来实现.穆乔方便.Java,唉,没有 - 更不用说强制你使用getter/setter的javabeans标准了. (19认同)
  • 虽然这确实是使用访问器的一个很好的理由,但许多编程环境和编辑器现在都支持重构(在IDE中或作为免费插件),这在某种程度上减少了问题的影响. (14认同)
  • @LBushkin - 假设您的代码不会被公开使用. (5认同)
  • 为什么所有关于删除IDE的讨论?为什么有人这样做?当然,我知道如何使用`ed`,有时,它是工作的工具.然而,编写软件不是其中之一. (4认同)
  • 该问题不是您需要使用set或get方法的指标.这表明整个团队需要将IDE从他们的手中打开,并教导他们在没有人的情况下编写,理解和维护OOP. (3认同)
  • @Zorf:如果你所有的'person`都用它来存储名字和姓氏,那么它只不过是一个(荣耀的)`struct`,因此,不是OOP,而是结构化编程.(无论是设置者还是获取者在这种场景中获得的任何东西,而不是明显地访问数据都充其量是可疑的:_A)_如何以字符串形式表示一个人的名字?而我在这里谈的是实际情况,而不是理论,因为_B)_在20年的实践中,我从未遇到过这样的情况.) (2认同)
  • 现在,如果我们谈论_real_ OOP(不是令人厌恶的Java),那么`person`就可以结婚来改变他/她的名字(`p1.marry(p2,p2.lastname())`),或离婚,或者无论是什么原因导致人们在不同的文化中改变自己 没有必要直接得到一个人的名字.请阅读[本评论](/sf/ask/109766401/?noredirect=1#comment16169586_1568230)中链接的两篇文章. (2认同)
  • Downvoted,因为假设,getter和setter应该比获取和设置值是完全错误的更多.这是可能的,肯定的,但我认为这是一个错误,或者至少一个代码味道.原因getter和setter方法是更继承和接口(不能界定公共领域). (2认同)

R. *_*des 345

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

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

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

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

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

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

class Foo {
public:
    int DaysLeft;
    int ContestantNumber;
};
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 这是一个很好的答案,但是当前时代已经忘记了"信息隐藏"是什么或它是什么.他们从未读过关于不变性的内容,并且在追求最敏捷的时候,从未画过那个定义了对象的合法状态是什么的状态转换图,从而没有得到什么. (14认同)
  • 很棒的答案(+1).我唯一的批评是,我花了好几读才弄清楚最后一段中的"验证"与前几段中的"验证"有何不同(后者在后一种情况下抛出,但在前者中推广); 在这方面调整措辞可能会有所帮助. (11认同)
  • 呃,我想你不知道类不变量是什么.如果它是一个非平凡的结构,它几乎意味着它具有不变的支持,并且如果没有这些,就无法设计它."有一个错误,一个成员更新错了." 也几乎读起来像"一个成员更新违反了类不变量".设计良好的类不允许客户端代码违反其不变量. (7认同)
  • "愚蠢的数据持有者+1应该看起来像愚蠢的数据持有者"不幸的是,现在每个人似乎都围绕着愚蠢的数据持有者设计一切,而且许多框架甚至需要它...... (4认同)
  • +1 是一个很好的答案,但是在“哑数据持有者”中,getter 和 setter 有一个优点,AFAIK 只有一个优点:大多数编译语言的 IDE 都有一个内置选项来“查找方法或属性的所有引用”。多年来,可以选择找到所有设置数据并过滤掉 getter 的位置,这为我节省了大量时间,而且我很难想象没有它就可以调试复杂的代码。 (3认同)
  • 一个关键的观察结果是 getter 比 setter 有用得多。getter 可以返回计算值或缓存的计算值。setter 所能做的只是一些验证,而不是更改 `private` 字段。如果一个类没有 setter,很容易使它不可变。 (2认同)
  • @user,“验证”在两个地方都意味着相同的事情。区别(如最后一段所解释的)是,如果您从一开始就实现验证,访问器是合理的,而不是“以防万一我想稍后添加验证”。 (2认同)

Kai*_*Kai 88

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

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

person.name = "Joe";
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这是反对语法糖的论据,而不是一般的反对者. (26认同)
  • 是.目前正在重构一个大型代码库,这一直是一场噩梦.getter和setter做得太多了,包括调用其他非常忙碌的getter和setter,最终将可读性降低到零.验证是使用访问器的一个很好的理由,但使用它们可以做更多的事情似乎可以消除任何潜在的好处. (7认同)
  • 真实而又真实,但是,它指出了为什么各个属性设置者为无效状态打开了大门,而绝对没有补救措施来确保允许抽象泄漏的任何给定对象的完整性. (6认同)
  • @Phil 我同意你的观点。我不相信隐藏方法调用。我只是想指出 Kai 的论点不是关于语法糖,而是更多关于 setter(包括看起来像方法调用的 setter)如何不应该产生副作用,例如更改对象中的其他数据成员或查询数据库。 (3认同)

Pet*_*r D 51

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

public void setSpeed(int speed) {
  if ( speed > 100 ) {
    this.speed = 100;
  } else {
    this.speed = speed;
  }
}
Run Code Online (Sandbox Code Playgroud)

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

我2美分:)

  • 追捕公共领域的每一次使用都不应该那么难.将其设为私有,让编译器找到它们. (60认同)
  • @Nathan:_Finding_其他用法不是问题._Changing_他们都是. (28认同)
  • 这是一个非常糟糕的例子!有人应该调用:`myCar.setSpeed(157);`并在几行之后`speed = myCar.getSpeed();`现在......我希望你快乐调试,同时试图理解为什么`speed == 100`当它应该是'157` (28认同)
  • @GraemePerrow不得不改变它们是一个优势,而不是一个问题:(如果你的代码假定速度可能高于100(因为,你知道,在你违反合同之前,它可以!)(` <200){do_something(); accele();}`) (22认同)
  • 这当然是真的,但为什么要比它设计的更难.获取/设置方法仍然是更好的答案. (11认同)
  • 您正在更改客户端和类之间的合同.客户可能会认为速度没有上限,他们的行为可能会中断.必须修复所有事件实际上是检查接口用法的好方法,确保新合同不会破坏客户端代码 (7认同)
  • 除非其他项目也使用该公共字段,即如果您将库共享给外部源 (4认同)
  • 追踪事物不应该成为当今时代的问题; 我使用IDE.我工作的项目类型永远不会与外部资源共享. (2认同)
  • @GraemePerrow Alt-Shift-R(eclipse - 其他IDE可能会采用不同的方式).轰隆隆,都变了). (2认同)
  • 我不认为将逻辑放入模型中是个好主意.使用验证器或设置服务的最大值. (2认同)

yeg*_*256 50

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

您可以在Elegant Objects的第3.5节(我的关于面向对象编程的书)中找到更多关于它们的信息.

  • 吸气者和制定者建议贫血的领域模型. (7认同)
  • 有趣的观点.但在大多数编程环境中,我们需要的是数据结构.以链接文章的"狗"为例.是的,你不能通过设置一个属性来改变真实世界的狗的体重......但是"新狗()"不是狗.保存关于狗的信息的对象.对于这种用法,能够纠正错误记录的重量是很自然的. (6认同)
  • 好吧,我告诉你*大多数*有用的程序不需要建模/模拟真实世界的对象.IMO,这根本不是编程语言.这是关于我们为其编写程序的内容. (3认同)
  • 现实世界与否,叶戈尔是完全正确的.如果你拥有的是真正的"结构",并且你不需要编写任何按名称引用它的代码,那么将它放在哈希表或其他数据结构中.如果你确实需要为它编写代码,那么把它作为类的成员,并将操作该变量的代码放在同一个类中,并省略setter和getter.PS.虽然我主要分享叶戈尔的观点,但我开始相信没有代码的带注释的bean是有些有用的数据结构 - 有时也需要getter,setter不应该存在. (3认同)

Tho*_*ens 37

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

例如,如果foo是公共的,我可以轻松地将其设置为null,然后其他人可以尝试在对象上调用方法.但它不再存在了!通过一种setFoo方法,我可以确保foo从未设置过null.

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


jcd*_*yer 28

取决于您的语言.你已经标记了这个"面向对象"而不是"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)
Run Code Online (Sandbox Code Playgroud)

  • @ ChssPly76-我不同意.我有同样多的控制权,就像它们是属性一样,因为我可以随时随地制作属性.使用样板getter和setter的属性与raw属性之间没有区别,除了raw属性更快,因为它使用底层语言,而不是调用方法.在功能上,它们是相同的.如果你认为括号(`obj.set_attr('foo')`)本质上优于等号(`obj.attr ='foo'`),那么封装可能被违反的唯一方法就是如此.公共访问是公共访问. (14认同)
  • @TimoHuovinen与Java中的用户有什么不同,假设`obj.setAttr('foo')`"只设置变量而没有发生任何其他事情"?如果它是一个公共方法,那么它是一个公共方法.如果你使用它来实现一些副作用,并且它是公开的,那么你最好能够指望所有工作*就好像只发生了预期的副作用*(带有*all*其他实现细节和其他副作用,资源使用,无论如何,隐藏在用户的关注之下).这与Python完全没有区别.Python的语法实现效果更简单. (9认同)
  • 是的,我在回答下面的评论中也说了很多.Java并不是唯一使用getter/setter作为拐杖的语言,就像Python不是唯一能够定义属性的语言一样.然而,重点仍然是 - "财产"不是"公共领域". (2认同)

Jor*_*lar 25

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


小智 22

谢谢,这真的澄清了我的想法.现在这里(差不多)10个(差不多)好的理由不使用getter和setter:

  1. 当你意识到你需要做的不仅仅是设置并获得价值时,你可以将该字段设为私有,这将立即告诉你你在哪里直接访问它.
  2. 您在那里执行的任何验证只能是无上下文的,实际上很少进行验证.
  3. 您可以更改正在设置的值 - 当调用者向您传递他们[震惊恐怖]希望您按原样存储的值时,这绝对是一场噩梦.
  4. 你可以隐藏内部表示 - 太棒了,所以你要确保所有这些操作都是对称的吗?
  5. 你已经将公共接口与工作表下的更改隔离开 - 如果你正在设计一个接口并且不确定是否可以直接访问某些东西,那么你应该继续进行设计.
  6. 有些库期望这样,但不是很多 - 反射,序列化,模拟对象都可以在公共字段中正常工作.
  7. 继承此类,您可以覆盖默认功能 - 换句话说,您可以通过隐藏实现但使其不一致来真正混淆调用者.

我刚离开的最后三个(N/A或D/C)......

  • 我认为关键的论点是,_"如果你正在设计一个界面并且不确定是否可以直接访问某些东西,那么你应该继续设计."_这是getter/setter最重要的问题:他们将类减少到仅仅(或多或少)公共字段的容器.但是,在_real_ OOP中,对象不仅仅是数据字段的容器.它封装状态和算法来操纵该状态.这个陈述的关键在于,状态应该被_encapsulated_并且只能被对象提供的算法操纵. (11认同)

kan*_*ioo 22

我知道这有点晚了,但我认为有些人对性能很感兴趣.

我做了一点性能测试.我写了一个"NumberHolder"类,它拥有一个Integer.您可以使用getter方法读取Integer,也可以使用 anInstance.getNumber()直接访问该数字anInstance.number.我的程序通过两种方式读取数字1,000,000,000次.该过程重复五次并打印时间.我得到以下结果:

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
Run Code Online (Sandbox Code Playgroud)

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

你看,吸气剂(几乎)总是快一点.然后我尝试了不同数量的周期.而不是100万,我使用了1000万和10万.结果:

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
Run Code Online (Sandbox Code Playgroud)

有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
Run Code Online (Sandbox Code Playgroud)

同样具有不同的循环量,吸气剂比常规方式快一点.我希望这对你有所帮助.

  • 有一个"明显的"开销,有一个函数调用来访问内存,而不是简单地加载一个对象的地址和添加一个偏移来访问成员.无论如何,VM可能会对您的吸气器进行平坦优化.无论如何,提到的开销不值得失去getter/setter的所有好处. (3认同)

小智 16

除非您当前交付需要,否则不要使用getter setter Ie不要过多考虑将来会发生什么,如果在大多数生产应用程序,系统中有任何改变请求的事情.

想想简单,容易,在需要时增加复杂性.

我不会利用对深度技术知识的企业主的无知,因为我认为这是正确的,或者我喜欢这种方法.

我有大量的系统编写没有getter setter只有访问修饰符和一些方法来验证n执行业务逻辑.如果你绝对需要的话.用什么.


Dev*_*ath 16

我们使用getter和setter:

  • 可重用性
  • 在编程的后期阶段执行验证

Getter和setter方法是访问私有类成员的公共接口.


封装口头禅

封装口头禅是将字段设为私有,方法是公共的.

Getter方法: 我们可以访问私有变量.

Setter方法: 我们可以修改私有字段.

尽管getter和setter方法没有添加新功能,但我们可以改变主意,稍后再回来制作该方法

  • 更好;
  • 更安全; 和
  • 快点.

在可以使用值的任何地方,可以添加返回该值的方法.代替:

int x = 1000 - 500
Run Code Online (Sandbox Code Playgroud)

使用

int x = 1000 - class_name.getValue();
Run Code Online (Sandbox Code Playgroud)

用外行人的话说

代表

假设我们需要存储此细节Person.这Person有领域name,agesex.这样做涉及到创建方法name,agesex.现在,如果我们需要建立另外一个人,有必要创造的方法name,age,sex从头再来.

我们可以class(Person)使用getter和setter方法创建一个bean ,而不是这样做.所以明天我们可以class(Person class)在需要添加新人时创建这个Bean的对象(参见图).因此,我们重用bean类的字段和方法,这要好得多.


qui*_*ker 15

它对于延迟加载很有用.假设有问题的对象存储在数据库中,除非您需要,否则您不希望得到它.如果对象是由getter检索的,那么内部对象可以为null,直到有人要求它为止,然后你可以在第一次调用getter时获取它.

我在一个项目中有一个基页类,它是从一些不同的Web服务调用中加载一些数据,但是这些Web服务调用中的数据并不总是在所有子页面中使用.Web服务,为了所有的好处,开创了"慢"的新定义,因此如果您不需要,您不希望进行Web服务调用.

我从公共字段移动到getter,现在getter检查缓存,如果没有,则调用Web服务.因此,通过一点包装,可以防止大量的Web服务调用.

所以getter使我无法在每个子页面上弄清楚我需要什么.如果我需要它,我会打电话给吸气器,如果我还没有它,它就会找到它.

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }
Run Code Online (Sandbox Code Playgroud)


Tho*_*sen 15

我花了很长时间考虑Java案例,我相信真正的原因是:

  1. 代码到接口,而不是实现
  2. 接口仅指定方法,而不指定字段

换句话说,您可以在界面中指定字段的唯一方法是提供用于编写​​新值的方法和用于读取当前值的方法.

那些方法是臭名昭着的吸气者和制定者......

  • 好的,第二个问题;如果它是一个您不向任何人导出源代码的项目,并且您可以完全控制源代码……您是否通过 getter 和 setter 获得了任何东西? (2认同)
  • 在任何非平凡的Java项目中,您需要对接口进行编码,以使事情易于管理和可测试(想想模型和代理对象).如果使用接口,则需要getter和setter. (2认同)

jde*_*aan 12

到目前为止我在答案中遗漏的一个方面是访问规范:

  • 对于成员,您只有一个访问规范,用于设置和获取
  • 对于制定者和吸气剂,您可以对其进行微调并单独定义


Haa*_*eit 11

编辑:我回答了这个问题,因为有很多人学习编程问这个问题,而且大多数答案在技术上非常有用,但如果你是新手,它们就不那么容易理解了.我们都是新手,所以我想我会尝试一个更新手友好的答案.

两个主要的是多态性和验证.即使它只是一个愚蠢的数据结构.

假设我们有这个简单的类:

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}
Run Code Online (Sandbox Code Playgroud)

一个非常简单的类,它包含多少液体,以及它的容量(以毫升为单位).

当我这样做时会发生什么:

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;
Run Code Online (Sandbox Code Playgroud)

好吧,你不会指望这样做,对吗?你希望在那里进行某种形式的理智检查.更糟糕的是,如果我从未指定最大容量怎么办?亲爱的,我们有问题.

但是还有另一个问题.如果瓶子只是一种容器怎么办?如果我们有几个容器,所有容器和液体的填充量怎么办?如果我们可以创建一个界面,我们可以让我们程序的其余部分接受该界面,瓶子,塑料桶和各种东西都可以互换.那不是更好吗?由于接口需要方法,这也是一件好事.

我们最终得到的结果如下:

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
}
Run Code Online (Sandbox Code Playgroud)

大!现在我们只需将Bottle更改为:

public class Bottle extends LiquidContainer {
  private int capacityMl;
  private int amountFilledMl;

  public Bottle(int capacityMl, int amountFilledMl) {
    this.capacityMl = capacityMl;
    this.amountFilledMl = amountFilledMl;
    checkNotOverFlow();
  }

  public int getAmountMl() {
    return amountFilledMl;
  }

  public void setAmountMl(int amountMl) {
     this.amountFilled = amountMl;
     checkNotOverFlow();
  }
  public int getCapacityMl() {
    return capacityMl;
  }

  private void checkNotOverFlow() {
    if(amountOfWaterMl > capacityMl) {
      throw new BottleOverflowException();
    }
}
Run Code Online (Sandbox Code Playgroud)

我将把BottleOverflowException的定义留给读者作为练习.

现在注意这是多么强大.我们现在可以通过接受LiquidContainer而不是Bottle来处理代码中的任何类型的容器.这些瓶子如何处理这些东西都可能有所不同.您可以将瓶子在更改时将其状态写入磁盘,或者保存在SQL数据库或GNU上的瓶子知道还有什么.

所有这些都可以采用不同的方式来处理各种各样的问题.Bottle只是检查它是否溢出它会抛出RuntimeException.但这可能是错误的做法.(关于错误处理有一个很有用的讨论,但我在这里有目的地保持它非常简单.评论中的人可能会指出这种简单方法的缺陷.;))

是的,似乎我们从一个非常简单的想法转变为快速获得更好的答案.

还有第三件事并不是每个人都能解决的问题:Getters和setter使用方法调用.这意味着它们看起来像其他地方的普通方法.而不是为DTO和东西提供奇怪的特定语法,你到处都有同样的东西.

  • “你可以让瓶子在更改时将它们的状态写入磁盘,或者让瓶子保存在 SQL 数据库中”我大声笑着说 xD。但无论如何,提出它是个好主意! (3认同)
  • 感谢我读过的接口的前半部分解释,没有任何对"遥控器"或"汽车"的引用 (2认同)

Joh*_*kin 10

在不支持"属性"(C++,Java)或需要在将字段更改为属性(C#)时重新编译客户端的语言中,使用get/set方法更容易修改.例如,将验证逻辑添加到setFoo方法不需要更改类的公共接口.

在支持"真实"属性的语言(Python,Ruby,也许是Smalltalk?)中,没有必要获取/设置方法.

  • 将验证逻辑添加到setFoo方法不需要在语言级别*更改类*的接口,但*它确实更改了实际的接口*,即契约,因为它改变了前提条件.为什么人们希望编译器在*为*时不将其视为重大变化*? (5认同)
  • 如答案中所述,需要重新编译是编译器可以让您了解可能的重大更改的一种方法.我写的几乎所有内容实际上都是"其他人的图书馆",因为我不是一个人工作的.我编写的代码具有项目中其他人将使用的接口.有什么不同?地狱,即使我将成为这些接口的用户,为什么我要保持我的代码以降低质量标准?我不喜欢使用麻烦的界面,即使我是那个写它们的人. (3认同)

Jus*_*ner 6

OO设计的基本原则之一:封装!

它为您提供了许多好处,其中之一就是您可以在幕后更改getter/setter的实现,但只要数据类型保持不变,任何具有该值的消费者都将继续工作.

  • 封装吸气剂和定型器提供的是可笑的薄.见[here](http://stackoverflow.com/questions/1568091/why-use-getters-and-setters#comment16169586_1568230). (23认同)
  • 如果合同发生变化,为什么要继续编制? (5认同)

and*_*s52 5

从面向对象设计的角度来看,这两种选择都可能削弱类的封装性,从而损害代码的维护。如需讨论,您可以查看这篇优秀的文章:http://tropicalprogrammer.com/ ?p=23


bob*_*obo 5

代码不断演变当您需要数据成员保护private时非常有用。最终,所有的类都应该是某种“小程序”,它们有一个定义良好的接口,你不能仅仅破坏.

也就是说,软件开发并不是要设定该类的最终版本,就好像您在第一次尝试时按下某个铸铁雕像一样。当您使用它时,代码更像是粘土。 随着您的开发以及对您正在解决的问题领域的更多了解而不断发展。在开发过程中,类之间可能会发生超出应有的交互(您计划排除的依赖关系)、合并在一起或分开。所以我认为争论归结为人们不想虔诚地写作

int getVar() const { return var ; }
Run Code Online (Sandbox Code Playgroud)

所以你有了:

doSomething( obj->getVar() ) ;
Run Code Online (Sandbox Code Playgroud)

代替

doSomething( obj->var ) ;
Run Code Online (Sandbox Code Playgroud)

不仅getVar()视觉上很嘈杂,而且它给人的错觉gettingVar()在某种程度上是一个比实际情况更复杂的过程。如果你的类有一个 passthru setter ,那么你(作为类作者)如何看待它的神圣性var对于你的类的用户来说尤其令人困惑 - 那么看起来你正在设置这些门来“保护”你坚持认为有价值的东西, ( 的神圣性var)但即使你承认,var任何人都可以进来并set var获得他们想要的任何价值,而你甚至没有偷看他们在做什么,因此保护并没有多大价值。

因此,我的编程如下(假设采用“敏捷”类型的方法 - 即当我编写代码时不知道它将做什么/没有时间或经验来规划精心设计的瀑布式界面集):

1) 从具有数据和行为的基本对象的所有公共成员开始。这就是为什么在我的所有 C++“示例”代码中,您会注意到我使用struct而不是class到处使用。

2)当一个对象的数据成员的内部行为变得足够复杂时(例如,它喜欢std::list以某种顺序保留内部),就会编写访问器类型函数。因为我自己编程,所以我并不总是private立即设置成员,但在类的演变过程中,成员将被“提升”为protectedprivate

3) 完全充实并且对其内部有严格规则的类(即它们确切地知道自己在做什么,并且您不能对其内部进行“操”(技术术语))被指定为class默认私有成员,并且只有少数经过挑选的成员才被允许public

我发现,在类演化的早期阶段,当大量数据成员被迁移、移动等时,这种方法可以让我避免坐在那儿认真地编写 getter/setter。


小智 5

考虑使用访问器的一个充分理由是没有属性继承。请参阅下一个示例:

public class TestPropertyOverride {
    public static class A {
        public int i = 0;

        public void add() {
            i++;
        }

        public int getI() {
            return i;
        }
    }

    public static class B extends A {
        public int i = 2;

        @Override
        public void add() {
            i = i + 2;
        }

        @Override
        public int getI() {
            return i;
        }
    }

    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.i);
        a.add();
        System.out.println(a.i);
        System.out.println(a.getI());
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

0
0
4
Run Code Online (Sandbox Code Playgroud)


aba*_*ert 5

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

  • 您正在处理的东西从概念上讲是一个属性,但是:
    • 您的语言没有属性(或某些类似的机制,例如Tcl的变量跟踪),或
    • 您的语言的财产支持不足以解决此用例,或者
    • 您的语言(或有时是您的框架)的惯用惯例会鼓励这种使用案例的使用者或使用者。

因此,这很少是一般的面向对象问题。这是一个特定于语言的问题,针对不同的语言(和不同的用例)有不同的答案。


从面向对象理论的角度来看,获取器和设置器是无用的。类的接口是它的作用,而不是状态。(如果不是,则您写错了类。)在非常简单的情况下,类所做的只是,例如,用直角坐标表示一个点,*属性是接口的一部分;吸气剂和塞特剂只是使这一点蒙上阴影。但是在非常简单的情况下,属性,getter和setter都不是接口的一部分。

换句话说,如果您认为类的使用者甚至不应该知道您有一个spam属性,更不用说随意更改它了,那么给他们提供一个set_spam方法是您要做的最后一件事。

*即使对于那个简单的类,您也不一定要允许设置xy值。如果这真的是一个类,它不应该有这样的方法translaterotate等等?如果因为您的语言没有记录/结构/命名元组而只是一堂课,那么这实际上不是OO问题。


但是没有人进行通用的OO设计。他们正在使用特定语言进行设计和实现。在某些语言中,获取器和设置器远非无用。

如果您的语言没有属性,则表示概念上某种属性但实际上是经过计算或验证等的唯一方法是通过getter和setter。

即使您的语言确实具有属性,在某些情况下也可能不够用或不合适。例如,如果要允许子类控制属性的语义,则在没有动态访问的语言中,子类不能用计算所得的属性代替属性。

至于“如果以后我想更改实现该怎么办?” 问题(在OP的问题和接受的答案中,用不同的措词重复多次):如果确实是纯粹的实现更改,并且从属性开始,则可以将其更改为属性,而不会影响界面。当然,除非您的语言不支持。所以这确实是同样的情况。

同样,遵循您使用的语言(或框架)的习惯用法也很重要。如果您用C#编写漂亮的Ruby风格的代码,那么除您之外的任何经验丰富的C#开发人员都将很难阅读它,这很糟糕。某些语言比其他语言具有更强的习惯文化。Java和Python在习惯用法的获取者方面处于相反的地位,碰巧拥有两种最强的文化,这并非巧合。

除了人类读者之外,还有一些库和工具可以期望您遵守这些约定,如果不遵循这些约定,将会使您的生活更加艰难。将Interface Builder窗口小部件挂接到ObjC属性之外的任何内容,或者使用某些没有getter的Java模拟库,只会使您的生活更加困难。如果这些工具对您很重要,请不要使用它们。


Pri*_*jee 5

GetterSetter用于实现面向对象编程的两个基本方面,它们是:

  1. 抽象
  2. 封装

假设我们有一个 Employee 类:

package com.highmark.productConfig.types;

public class Employee {

    private String firstName;
    private String middleName;
    private String lastName;

    public String getFirstName() {
      return firstName;
    }
    public void setFirstName(String firstName) {
       this.firstName = firstName;
    }
    public String getMiddleName() {
        return middleName;
    }
    public void setMiddleName(String middleName) {
         this.middleName = middleName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getFullName(){
        return this.getFirstName() + this.getMiddleName() +  this.getLastName();
    }
 }
Run Code Online (Sandbox Code Playgroud)

这里,全名的实现细节对用户隐藏,并且用户无法直接访问,这与公共属性不同。

  • 对我来说,拥有大量没有任何独特作用的 getter 和 setter 是没有用的。getFullName 是一个例外,因为它做了其他事情。仅公开三个变量,然后保留 getFullName 将使程序更易于阅读,但仍然隐藏了全名内容。一般来说,如果有的话,我对 getter 和 setter 完全没问题。他们做了一些独特的事情和/或 b. 你只有一个,是的,你可以进行公开决赛等等,但是不 (3认同)
  • @AndrewHows,从我在现实生活中看到的情况来看,当人们更改类的内部结构时,他们也会更改接口并对所有代码进行大规模重构 (3认同)
  • 这样做的好处是您可以更改类的内部,而无需更改接口。假设您有一个属性,而不是三个属性 - 字符串数组。如果您一直在使用 getter 和 setter,则可以进行更改,然后更新 getter/setter 以了解名称 [0] 是名字,名称 [1] 是中间名称,等等。但是如果您只使用公共属性,您还必须更改访问 Employee 的每个类,因为他们一直使用的firstName 属性不再存在。 (2认同)

adi*_*ath 5

数据结构和对象之间存在差异。

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

一个对象不应该暴露它的内部结构,但应该暴露它的行为,这也被称为得墨忒耳法则

大多数情况下,DTO 更多地被认为是一种数据结构,而不是对象。他们应该只暴露他们的数据而不是行为。在 DataStructure 中使用 Setter/Getter 将暴露行为而不是其中的数据。这进一步增加了违反德墨忒尔法则的几率。

鲍勃叔叔在他的《干净的代码》一书中解释了德米特法则。

有一个著名的启发式,称为德米特定律,它说模块不应该知道它所操作的对象的内部结构。正如我们在上一节中看到的,对象隐藏其数据并公开操作。这意味着对象不应通过访问器公开其内部结构,因为这样做是公开而不是隐藏其内部结构。

更准确地说,德米特定律规定类 C 的方法 f 只能调用以下方法:

  • C
  • 由 f 创建的对象
  • 作为参数传递给 f 的对象
  • 保存在 C 的实例变量中的对象

该方法不应调用任何允许的函数返回的对象上的方法。换句话说,与朋友交谈,而不是与陌生人交谈。

因此,根据此,LoD 违规的示例是:

final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath();
Run Code Online (Sandbox Code Playgroud)

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

相反,我们可以做这样的事情。

final String outputDir = ctxt.options.scratchDir.absolutePath;
Run Code Online (Sandbox Code Playgroud)

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

受到 Robert C. Martin(鲍勃叔叔)的《干净代码》的启发


use*_*042 5

如果您不需要任何验证,甚至不需要维护状态,即一个属性依赖于另一个属性,那么我们需要在一个属性发生更改时维护状态。您可以通过公开字段而不使用 getter 和 setter 来保持简单。

我认为,随着程序的增长,面向对象编程会让事情变得复杂,这对开发人员来说扩展成为噩梦。

一个简单的例子;我们从 xml 生成 C++ 标头。标头包含不需要任何验证的简单字段。但仍然像 OOPS 访问器一样,我们按如下方式生成它们。

const Filed& getfield() const
Field& getField() 
void setfield(const Field& field){...} 
Run Code Online (Sandbox Code Playgroud)

这是非常冗长的并且不是必需的。一个简单的

struct 
{
   Field field;
};
Run Code Online (Sandbox Code Playgroud)

就足够了并且可读。函数式编程没有数据隐藏的概念,它们甚至不需要它,因为它们不会改变数据。