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


1540

使用仅获取和设置的getter和setter而不是仅对这些变量使用公共字段的优点是什么?

如果getter和setter所做的不只是简单的get / set,我可以很快地弄清楚这一点,但是我不清楚如何做到这一点:

public String foo;

比以下任何方面都更糟糕:

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

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


6
@Dean J:与许多其他问题重复:stackoverflow.com/search?
Asaph,2009年

8
当然,当对象不需要更改属性时,两者同样不好。我宁愿将所有内容都设为私有,然后在有用时添加getter,并在需要时添加setter。
Tordek

26
Google“访问者是邪恶的”
OMG Ponies,2009年

34
如果您恰巧正在编写功能代码或不可变对象,则“访问者是邪恶的”。如果您恰巧正在编写有状态的可变对象,那么它们非常重要。
Christian Hayter,

Answers:


980

实际上,有很多充分的理由考虑使用访问器,而不是直接公开类的字段-除了封装的论点之外,并使将来的更改更容易。

这是我知道的一些原因:

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

59
+1。只需添加:允许延迟加载。允许写时复制。
NewbiZ

6
@bjarkef:我相信public访问器方法会导致代码重复并公开信息。编组(序列化)不需要公共访问器。在某些语言(Java)中,public在不破坏依赖类的情况下,get访问器不能更改返回类型。而且,我相信它们与OO原则背道而驰。另请参阅:stackoverflow.com/a/1462424/59087
戴夫·贾维斯

15
@sbi:即使在设计良好的框架中,使用属性设置器也可以买到的一件事是,当属性更改时,可以让对象通知另一对象。
2012年

22
@supercat:但是,一般而言,我不是在宣传公共数据。其他对象的通知也可以从实际方法中完成,该方法在具有(或多或少)公共数据字段的纯数据容器上提供重要的抽象。与其说plane.turnTo(dir); plane.setSpeed(spd); plane.setTargetAltitude(alt); plane.getBreaks().release();我要说plane.takeOff(alt)takingOff我不关心要更改哪些内部数据字段以使飞机进入模式。还有breaks该方法通知的其他对象()我也不想知道。
2012年

8
@BenLee:我真的不知道该怎么说。“访问器”只是“获取器/设置器”的同义词,在我的前两个评论中,我解释了为什么这些滥用了“ OOP”一词。如果您需要深入了解它并直接操纵状态,那么就该术语而言,它不是对象。
2012年

480

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


84
我坐在那里盯着一个从未用过的50万行应用程序。就是说,如果只需要一次,它将开始引起维护噩梦。足够我打勾了。
院长J,

20
我只能羡慕您和您的应用程序:-)也就是说,它实际上也取决于您的软件堆栈。例如,Delphi(和C#-我认为吗?)允许您将属性定义为一等公民,他们可以在这些公民最初直接读取/写入字段,但是-如果需要,也可以通过getter / setter方法来执行。Mucho方便。,, Java不会-更不用说强制您也使用getters / setters的javabeans标准了。
ChssPly76

14
尽管这确实是使用访问器的一个很好的理由,但是许多编程环境和编辑器现在都提供了对重构的支持(在IDE中或作为免费的外接程序),从而在某种程度上减少了问题的影响。
LBushkin

62
听起来像是过早的优化
cmcginty 2012年

41
当您想知道是否要实现getter和setter时,您需要问的问题是:为什么类的用户根本需要访问该类的内部函数?究竟是直接执行操作还是由薄的伪层进行屏蔽都没关系-如果用户需要访问实现细节,则表明该类没有提供足够的抽象。另请参阅此评论
2012年

357

一个公共领域并不比没有返回或分配给它做任何事情的getter / setter对更糟糕。首先,很明显(在大多数语言中)没有功能上的区别。任何差异都必须在其他因素上,例如可维护性或可读性。

并非经常提到吸气剂/设置剂对的优点。有人声称您可以更改实现,而不必重新编译客户。据说,setter可以让您稍后添加诸如验证之类的功能,而您的客户甚至不需要了解它。但是,向验证器添加验证是对其先决条件的更改,这是对先前合同的违反,这很简单,就是“您可以在此处放置任何东西,以后可以从获取器中获得相同的东西”。

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

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

同样的论点也适用于这些传递性getter / setter对的其他假定优点:如果您以后决定更改所设置的值,则您正在违反合同。如果您以某种无害的修改(例如日志记录或其他不可观察的行为)的方式覆盖了派生类中的默认功能,则您正在破坏基类的契约。这违反了《里斯科夫可替代性原则》,后者被视为面向对象的宗旨之一。

如果一个类的每个字段都具有这些愚蠢的获取器和设置器,则该类将没有任何不变式,也没有合同。那真的是面向对象的设计吗?如果班上所有的人都是那些getter和setter,那只是一个哑数据持有人,而哑数据持有人应该看起来像哑数据持有人:

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

在此类中添加传递式获取/设置对不会增加任何价值。其他类应该提供有意义的操作,而不仅仅是字段已经提供的操作。这样便可以定义和维护有用的不变量。

客户:“我可以用这个类的对象做什么?”
设计器:“您可以读写几个变量。”
客户:“哦,太酷了,我猜是吗?”

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


12
好答案(+1)。我唯一的批评是,我花了几读才能弄清楚最后一段中的“验证”与前几句中的“验证”有何不同(您在后一种情况中将其丢弃,而在前一种情况中则予以提升);调整措辞可能会在这方面有所帮助。
Lightness Races in Orbit

14
这是一个很好的答案,但是遗憾的是,当前时代已经忘记了“信息隐藏”的含义或目的。他们从不了解不变性,在追求最敏捷的过程中,从来没有画过状态转换图来定义对象的合法状态,什么不是。
Darrell Teague

2
一个主要的观察结果是,吸气剂比二传手更有用。吸气剂可以返回计算值或缓存的计算值。设置员可以做的只是进行一些验证,而不是更改private字段。如果一个类没有setter,很容易使其不可变。
2014年

7
嗯,我猜你不知道什么是类不变式。如果它是一个非平凡的结构,则几乎意味着它具有不变性,如果没有这些,就无法设计它。“有一个错误,一个成员被更新为错误。” 也很像“成员更新违反了类不变式”之类的内容。设计良好的类不允许客户端代码违反其不变式。
R. Martinho Fernandes

5
+1为“哑数据持有人应该看起来像哑数据持有人”不幸的是,如今,每个人似乎都围绕着哑数据持有人设计了所有东西,而且很多框架甚至都要求它……
Joeri Hendrickx

92

很多人都在谈论吸气剂和吸气剂的优点,但我想扮演魔鬼的拥护者。现在,我正在调试一个非常大的程序,程序员在该程序中决定使所有东西都变得吸气和用气。这看起来不错,但这是反向工程的噩梦。

假设您正在浏览数百行代码,就会发现:

person.name = "Joe";

这是一段漂亮的代码,直到您意识到它的设置者。现在,您遵循该设置器,发现它还设置了person.firstName,person.lastName,person.isHuman,person.hasReallyCommonFirstName,并调用person.update(),这会将查询发送到数据库,等等。哦,那是发生内存泄漏的位置。

乍看之下,了解本地代码是具有良好可读性的重要属性,而吸气剂和装料器往往会破坏这种特性。这就是为什么我会尽量避免使用它们,并尽量减少使用它们时的行为。


8
是。当前正在重构大型代码库,这是一场噩梦。这些getter和setter会做太多事情,包括调用其他非常忙碌的getter和setter,最终使可读性降低为零。验证是使用访问器的重要原因,但是使用访问器做的事情远不止于此,因此似乎没有任何潜在的好处。
Fadecomic

31
这是针对语法糖的争论,而不是针对一般的二传手。
Phil

6
确实,但此外,它指出了为什么各个属性设置器如何打开无效状态的门,而对于确保允许抽象泄漏的任何给定对象的完整性,绝对没有任何补救措施。
Darrell Teague

“这是一段漂亮的代码,直到您意识到它是一个棘手的问题为止” –嘿,原始文章是关于Java还是关于C#的?:)
Honza Zidek

我完全同意可读性。完全明确的是什么时候person.name = "Joe";被调用。信息的方式没有问题,语法高亮显示它是一个字段,并且重构更简单(不需要重构两个方法和一个字段)。其次,可以忘记所有那些脑部浪费的思维,例如“ getIsValid()”或“ isValid()”等。我想不起来有一次我被getter / setter从错误中救出了。
威尔

53

在纯粹的面向对象的世界中,吸气剂和吸气剂是一种可怕的反模式。阅读本文:Getters / Setters。邪恶。期间。简而言之,他们鼓励程序员考虑数据结构中的对象,而这种类型的思考是纯粹的过程性的(例如在COBOL或C中)。在面向对象的语言中,没有数据结构,只有暴露行为的对象(没有属性/属性!)

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


7
吸气剂和吸气剂建议使用贫血领域模型。
拉德瓦尔德2014年

7
有趣的观点。但是在大多数编程环境中,我们需要的是数据结构。以链接的文章的“狗”为例。是的,您无法通过设置属性来更改现实世界中狗的体重……但a new Dog()不是狗。它是保存有关狗的信息的对象。对于这种用法,很自然就能纠正错误记录的重量。
史蒂芬C

3
好吧,我告诉你,大多数有用的程序都不需要建模/模拟现实世界的对象。IMO,这根本不是关于编程语言的。这是关于我们编写程序的目的。
Stephen C

3
不管现实世界与否,yegor是完全正确的。如果您拥有的是真正的“结构”,而无需编写任何通过名称引用它的代码,则将其放入哈希表或其他数据结构中。如果确实需要为其编写代码,则将其作为类的成员,然后将操纵该变量的代码放入同一类中,并省略setter和getter。PS。尽管我大多数都同意yegor的观点,但我已经相信没有代码的带注释的Bean是有些有用的数据结构-有时也需要使用getter,而不应该使用setter。
比尔K

1
我被带走了-整个答案虽然正确且相关,但并没有直接解决这个问题。也许应该说“两个setter / getter和公共变量都是错误的...”……绝对要明确地说,绝对不要使用Setters和可写的public变量,而getter与public final变量几乎一样,并且有时是必不可少的但两者都不比对方好得多。
Bill K

52

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

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

现在,如果您在代码中的任何地方都使用公共字段,然后又意识到需要上述要求怎么办?有趣地寻找公共领域的每一种用法,而不仅仅是修改您的二传手。

我的2美分:)


61
寻找公共领域的各种用法并不难。将其设为私有,让编译器找到它们。
内森·费尔曼

12
当然是对的,但是为什么要使其比设计的难度更大。获取/设置方法仍然是更好的答案。
Hardryv

28
@Nathan:查找其他用法不是问题。改变它们都是。
Graeme Perrow,2009年

22
@GraemePerrow必须全部更改都是一个优点,而不是一个问题:(如果您的代码假定速度可能高于100,该怎么办(因为您知道,在违反合同之前,它可能会!)(while(speed < 200) { do_something(); accelerate(); }
R。 Martinho Fernandes

29
这是一个非常糟糕的例子!有人应该打电话给我:myCar.setSpeed(157);几行之后speed = myCar.getSpeed();,现在...希望您能在调试的同时尝试理解为什么speed==100应该157
这么做

38

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

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

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


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)

2
是的,我在回答下方的评论中已经说了很多。Java不是将getters / setters用作拐杖的唯一语言,就像Python并非唯一能够定义属性的语言一样。然而,要点仍然存在-“财产”与“公共领域”不同。
ChssPly76

1
@jcd-一点也不。您将通过公开公共字段来定义“接口”(此处最好使用公共API)。一旦完成,就没有回头路了。属性不是字段,因为它们为您提供了一种拦截尝试访问字段的机制(通过将它们路由到定义了方法的方法);但是,无非就是getter / setter方法上的语法糖。这是非常方便的,但是它不会改变底层的范式-暴露无法控制对它们的访问的字段会违反封装原理。
ChssPly76

14
@ ChssPly76-我不同意。我拥有与属性一样多的控制权,因为我可以在需要时使它们成为属性。使用样板化getter和setter的属性与raw属性之间没有什么区别,除了raw属性更快之外,因为它利用了底层语言,而不是调用方法。在功能上,它们是相同的。违反封装的唯一方法是,如果您认为括号(obj.set_attr('foo'))本质上优于等号(obj.attr = 'foo')。公共访问是公共访问。
jcdyer

1
@jcdyer是可以控制的,但是可读性却不那么高,其他人经常错误地认为obj.attr = 'foo'只是设置了变量而没有其他任何事情
Timo Huovinen 2013年

9
@TimoHuovinen与Java中的用户有什么不同,它假定obj.setAttr('foo')“只是设置变量而没有其他任何事情”?如果是公共方法,那么它是公共方法。如果您使用它来实现某种副作用,并且它是公开的,那么您最好能够指望所有有效的工作,就好像只是发生了预期的副作用(以及所有其他实现细节和其他副作用,资源使用,无论如何) ,从用户的关注中隐藏)。对于Python,这绝对没有什么不同。Python的语法可以达到这种效果,只是比较简单。
2014年

25

好吧,我只是想补充一点,即使有时候它们对于变量/对象的封装和安全性是必需的,如果我们想编写一个真正的面向对象程序,那么我们就需要停止过度使用这些访问器,因为有时我们依赖很多何时不需要它们,这几乎与我们将变量公开一样。


24

谢谢,这确实澄清了我的想法。现在(几乎)有10个(几乎)充分的理由不使用getter和setter:

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

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


11
我认为关键的论据是:“如果您正在设计一个接口,但是不确定直接访问某些东西是否可以,那么您应该继续进行设计。” 这是使用getter / setter的人最重要的问题:他们将一个类减少为仅(或多或少)公共领域的容器。但是,在实际的 OOP中,对象不仅仅是数据字段的容器。它封装了状态和用于操纵该状态的算法。该语句的关键是状态应该被封装并且只能由对象提供的算法来操纵。
2012年

22

我知道有些晚了,但我认为有些人对性能感兴趣。

我做了一些性能测试。我写了一个“ NumberHolder”类,它拥有一个整数。您可以使用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

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

您会发现,吸气剂总是(几乎)总是快一点。然后我尝试了不同数量的循环。我使用了1000万和10万,而不是100万。结果:

一千万个周期:

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

一千万次循环,时间几乎相同。以下是十万(十万)个周期:

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

同样,在不同的循环次数下,吸气剂比常规方法要快一点。希望这对您有所帮助。


3
有一个“值得注意的”开销,需要调用函数来访问内存,而不是简单地加载对象的地址并添加偏移量以访问成员。无论如何,VM都有可能对您的吸气剂进行平面优化。无论如何,所提到的开销并不值得失去getter / setter的所有好处。
亚历克斯

16

除非当前交付需要,否则不要使用getters setter,例如,对于大多数生产应用程序,系统中的更改请求,如果有什么要更改的内容,请不要考虑太多。

简单,轻松地思考,并在需要时增加复杂性。

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

我编写了庞大的系统,而没有使用访问修饰符和一些方法来验证n执行biz逻辑的getters setter。如果您绝对需要。使用任何东西。


16

我们使用getter和setter方法:

  • 为了可重用
  • 在以后的编程阶段执行验证

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


封装口头禅

封装的原则是使字段私有和方法公开。

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

设置方法: 我们可以修改私有字段。

即使getter和setter方法未添加新功能,我们也可以稍后改变主意以使该方法

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

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

int x = 1000 - 500

采用

int x = 1000 - class_name.getValue();

用外行的话

代表“人”类

假设我们需要存储this的细节Person。这Person有田nameagesex。这样做涉及到创建方法nameagesex。现在,如果我们需要建立另外一个人,有必要创造的方法nameagesex从头再来。

除了这样做,我们还可以class(Person)使用getter和setter方法创建一个bean 。因此,明天我们class(Person class)只要有需要添加新人员时,就可以创建该Bean的对象(参见图)。因此,我们正在重用bean类的字段和方法,这要好得多。


15

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

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

换句话说,在接口中指定字段的唯一方法是提供一种写入新值的方法和一种读取当前值的方法。

这些方法是臭名昭著的getter和setter。


1
好,第二个问题;如果您的项目是不向任何人导出源代码,并且您完全控制了源代码,那么……您会通过getter和setter获得任何好处吗?
院长J,

2
在任何不平凡的Java项目中,都需要对接口进行编码,以使事物可管理和可测试(请考虑模型和代理对象)。如果使用接口,则需要getter和setter。
托尔比约恩Ravn的安徒生

15

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

我在一个交付给我的项目中有一个基页类,该基类从几个不同的Web服务调用中加载一些数据,但是这些Web服务调用中的数据并不总是在所有子页面中都使用。Web服务具有所有优点,它们开创了“慢速”的新定义,因此您不必调用Web服务。

我从公共领域转移到吸气剂,现在吸气剂检查缓存,如果不存在,请调用Web服务。因此,只需稍作封装,就可以避免许多Web服务调用。

因此,getter使我免于尝试在每个子页面上弄清楚我将需要什么。如果需要的话,我给吸气剂打电话,如果我还没有吸气剂,它将去找我。

    protected YourType _yourName = null;
    public YourType YourName{
      get
      {
        if (_yourName == null)
        {
          _yourName = new YourType();
          return _yourName;
        }
      }
    }

那么,getter会调用setter吗?
icc97 2014年

我已经添加了一个代码示例,该示例说明了过去的操作方式-本质上,您将实际的类存储在受保护的成员中,然后将该受保护的成员返回到get访问器中,如果未初始化则将其初始化。
quillbreaker 2014年


13

到目前为止,我错过了答案中的一个方面,即访问规范:

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

13

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

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

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

public class Bottle {
  public int amountOfWaterMl;
  public int capacityMl;
}

一个非常简单的类,其中包含有多少液体以及其容量是多少(以毫升为单位)。

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

Bottle bot = new Bottle();
bot.amountOfWaterMl = 1500;
bot.capacityMl = 1000;

好吧,您不会期望它起作用,对吗?您希望进行某种健全性检查。更糟糕的是,如果我从未指定最大容量怎么办?哦,亲爱的,我们有问题。

但是,还有另一个问题。如果瓶子只是一种容器怎么办?如果我们有几个容器,都装有容量和数量的液体怎么办?如果我们可以仅创建一个接口,那么我们可以让程序的其余部分接受该接口,并且瓶子,塑料桶和各种物品都可以互换使用。那会更好吗?由于接口需要方法,因此这也是一件好事。

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

public interface LiquidContainer {
  public int getAmountMl();
  public void setAmountMl(int amountMl);
  public int getCapacityMl();
}

大!现在我们将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();
    }
}

我将把BottleOverflowException的定义留给读者练习。

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

所有这些可能都有不同的方式来处理各种杂音。瓶子只是检查,如果溢出,则抛出RuntimeException。但这可能是错误的事情。(关于错误处理,有一个有用的讨论,但是我在这里故意使它非常简单。有评论的人可能会指出这种简单化方法的缺陷。))

是的,似乎我们已经从一个非常简单的想法变成了迅速获得更好的答案。

另请注意,您不能更改瓶子的容量。现在它被固定在石头上了。您可以通过将int声明为final来实现。但是,如果这是一个列表,则可以将其清空,向其中添加新内容,等等。您不能限制接触内脏。

还有不是所有人都解决的第三件事:getter和setter使用方法调用。这意味着它们看起来像其他任何地方的普通方法一样。您不必在DTO和内容上使用怪异的特定语法,而到处都有相同的东西。


2
感谢您对我已阅读的界面进行的前半个体面的解释,但不包括对“远程控件”或“汽车”的任何引用
djvs

1
“您可以让瓶子在状态更改时将其状态写入磁盘,或者将瓶子保存在SQL数据库中”我对xD如此讨厌。但是无论如何,介绍它的好主意!
Xerus

10

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

在支持“真实”属性的语言(Python,Ruby,也许是Smalltalk?)中,没有任何获取/设置方法的意义。


1
回复:C#。如果您将功能添加到get / set中,是否仍然需要重新编译?
steamer25

@ steamer25:对不起,打错了。我的意思是,该类的客户必须重新编译。
约翰·米利金

5
将验证逻辑添加到setFoo方法将不需要在语言级别更改类的接口,但是它确实会更改实际的接口(也称为协定),因为它会更改前提条件。一个为什么要编译器不把它看成一个重大更改,当它
R. Martinho Fernandes

@ R.MartinhoFernandes如何解决这个“破碎”的问题?编译器无法判断它是否中断。当您为其他人编写库时,这只是一个问题,但是您将它视为通用zOMG真是龙!
Phil

3
如答案中所述,要求重新编译是编译器可以使您知道可能的重大更改的一种方法。实际上,我写的几乎所有内容都是“他人的图书馆”,因为我不是一个人工作。我编写的代码具有项目中其他人将使用的接口。有什么不同?地狱,即使我将成为这些界面的用户,为什么还要保留我的代码以降低质量标准?我不喜欢使用麻烦的界面,即使我是编写它们的人。
R. Martinho Fernandes

6

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

它给您带来许多好处,其中之一就是您可以在后台更改getter / setter的实现,但是只要数据类型保持不变,任何具有该价值的使用者都将继续工作。


23
封装的吸气剂和装料器的厚度很薄。看这里
2012年

如果您的公共接口声明“ foo”的类型为“ T”,并且可以设置为任何值,则您永远不能更改它。稍后您不能决定使其类型为“ Y”,也不能强加大小限制之类的规则。因此,如果您的公共获取/设置没有做太多的设置/获取,那么您就不会获得公共领域不会提供的任何东西,并且使用起来更加麻烦。如果您有约束,例如可以将对象设置为null或值必须在范围内,则可以,需要使用public set方法,但是您仍然会通过设置该值来提供合同,改变
thecoshman

为什么合同不能更改?
Phil

5
如果合同发生变化,为什么事情还要继续编译?
R. Martinho Fernandes

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模拟库,只会使您的生活更加困难。如果这些工具对您很重要,请不要使用它们。



3

Getter和setter方法是访问器方法,这意味着它们通常是更改私有类成员的公共接口。您可以使用getter和setter方法定义属性。即使在类中将getter和setter方法定义为方法,也可以将它们作为属性访问。类之外的那些属性可以具有与类中的属性名称不同的名称。

使用getter和setter方法有一些优点,例如使您能够创建具有可以像属性一样访问的复杂功能的成员的能力。它们还允许您创建只读和只写属性。

尽管getter和setter方法很有用,但您仍应注意不要过度使用它们,因为,除其他问题外,它们在某些情况下会使代码维护更加困难。此外,它们还提供对您的类实现的访问,例如公共成员。OOP实践不鼓励直接访问类中的属性。

在编写类时,总是鼓励您尽可能多地将实例变量设为私有,并相应地添加getter和setter方法。这是因为有时您可能不想让用户更改类中的某些变量。例如,如果您有一个私有的静态方法来跟踪为特定类创建的实例数,则您不希望用户使用代码来修改该计数器。每当调用该变量时,只有构造函数语句应使该变量递增。在这种情况下,您可能会创建一个私有实例变量,并仅允许将getter方法用于计数器变量,这意味着用户只能使用getter方法来检索当前值,而他们将无法设置新值使用setter方法。


3

代码不断发展private非常适合需要数据成员保护的情况。最终,所有类都应该是具有明确定义的接口的“小程序” ,您不能仅仅将它们与

也就是说,软件开发并不是要设置课程的最终版本,就好像您在第一次尝试时按了一些铸铁雕像一样。使用它时,代码更像黏土。 它随着您的发展而发展,并了解您要解决的问题领域的更多信息。在开发过程中,类可能会相互干扰(您打算排除的依赖性),合并在一起或分裂。因此,我认为辩论归结为那些不想信奉宗教信仰的人

int getVar() const { return var ; }

所以你有了:

doSomething( obj->getVar() ) ;

代替

doSomething( obj->var ) ;

不仅getVar()视觉上嘈杂,而且给人一种错觉,使错觉gettingVar()比实际情况复杂得多。如果您(作为班级作者)如何看待圣贤,var如果班级中有通行证的话,这会让您的班级用户特别困惑-那么看来您是在竖起这些门来“保护”您坚持认为有价值的东西, (的神圣性var),但即使您承认var,任何人只要有能力进来,set var达到他们想要的任何价值而没有您偷看他们正在做的事情。

因此,我按以下方式进行编程(假设采用“敏捷”类型的方法-即,当我编写的代码不完全知道将要做什么/没有时间或经验来计划精心设计的瀑布样式接口集时):

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

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

3)完全充实并且对内部构造有严格规定的类(即,他们确切地知道自己在做什么,并且您不要对其内部进行“操弄”(技术术语))被赋予class名称,默认的私有成员,并且只有少数几个成员被允许成为public

我发现这种方法使我避免在课堂发展的早期阶段,当许多数据成员被移出,调动等时,避免坐在那里,认真地写getter / setter。


1
“ ...一个定义明确的界面,您不能仅仅使用它的内部结构”和设置器中的验证。
Agi Hammerthief 2014年

3

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

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());
    }
}

输出:

0
0
4

3

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

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


1
对我来说,拥有大量无用的吸气剂和吸气剂是没有用的。getFullName是一个例外,因为它还会执行其他操作。仅公开三个变量,然后保留getFullName将使程序更易于阅读,但仍然隐藏了全名。通常,如果我使用getter和setter完全没问题。他们做一些独特的事情和/或b。你只有一个,是你可以有一个公开的最终和所有的,但罗
FacelessTiger

1
这样做的好处是您可以更改类的内部,而无需更改接口。假设不是三个属性,而是一个-字符串数组。如果您一直在使用getter和setter,则可以进行更改,然后更新getter / setter,以知道names [0]是名字,names [1]是中间名,等等。但是,如果您只是使用公共属性,您还必须更改每个访问的Employee类,因为他们使用的firstName属性不再存在。
Andrew Hows

1
@AndrewHows从我在现实生活中所见,当人们更改类的内部结构时,他们还更改了界面并对所有代码进行了大的重构
Eildosa

2

(在支持属性的语言中)另一种用法是,setter和getter可以暗示操作是不平凡的。通常,您希望避免在属性中执行任何计算量大的事情。


我从没想到吸气器或装夹器会是一个昂贵的操作。在这种情况下更好地使用工厂:使用你所需要的所有制定者,然后调用昂贵executebuild方法。
休伯特·格热斯科维克

2

getters / setters的一个相对较现代的优点是,它使得在标记(索引)代码编辑器中浏览代码更加容易。例如,如果要查看谁设置成员,则可以打开设置器的呼叫层次。

另一方面,如果成员是公共成员,则该工具将无法过滤对该成员的读/写访问。因此,您必须仔细考虑该成员的所有使用情况。


您可以做正确的选择>像使用getter / setter一样查找成员的用法
Eildosa

2

在面向对象的语言中,方法及其访问修饰符声明该对象的接口。在构造函数与访问器和mutator方法之间,开发人员可以控制对对象内部状态的访问。如果仅将变量声明为public,则没有任何方法可以调节该访问。当我们使用设置器时,我们可以限制用户所需的输入。意味着该变量的提要将通过适当的渠道提供,并且该渠道是我们预先定义的。因此,使用二传手更安全。


1

另外,这是为了“防止将来”您的班级。特别是,从字段更改为属性是ABI的中断,因此,如果您以后决定需要更多的逻辑,而不仅仅是“设置/获取字段”,那么您就需要中断ABI,这当然会为任何问题带来问题。否则已经针对您的班级进行了编译。


2
我想,改变吸气剂或塞特剂的行为并不是一个突破性的改变。</ sarcasm>
R. Martinho Fernandes

@ R.MartinhoFernandes不一定总是必须的。TBYS
Phil,

1

我只想提出注释的想法:@getter和@setter。使用@getter,您应该可以obj = class.field,但不能使用class.field = obj。使用@setter,反之亦然。使用@getter和@setter,您应该可以同时做到。通过在运行时不调用琐碎的方法,可以保留封装并减少时间。


1
它将在运行时使用“简单方法”实现。实际上,这可能是不平凡的。
2013年

今天,这可以通过注释预处理器来完成。
托尔比约恩Ravn的安徒生
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.