公共数据成员 vs 获取器、设置器

86

我目前在使用Qt和C++开发。我的类有私有成员变量和公共成员函数。类中有为数据成员提供的公共getter和setter。

我的问题是,如果我们在类中有为数据成员提供的getter和setter,那么将这些数据成员设置为私有有什么意义?我同意在基类中设置私有成员变量是合理的。但除此之外,对于我来说,让这些成员变量及其getter和setter都是私有的似乎不太合理。

或者我们是否可以将所有变量设置为public,这样就没有必要再使用getter和setter了?这样做是否是一个好习惯呢?我知道将成员变量设置为私有可以确保数据抽象性,但使用getter和setter实际上很容易访问这些变量。对此的任何指导都欢迎。


1
为了控制一个类中的变量在另一个类中的修改,我们可以将该变量设置为私有,以此来控制其值被修改的方式。例如,如果在另一个类中使用setter将变量修改为负数,我们可以抛出异常来防止其被设置为负数。为了实现这一点,该变量必须被转换为私有,并且要访问该私有变量,我们可以使用getter。 - karthik gorijavolu
15个回答

85

不用二者,你应该拥有执行任务的方法。如果其中一个任务恰好对应于特定的内部变量,那就太好了,但是不应该向类的用户透露这一点。

私有数据是私有的,因此您可以随时替换实现(并可以进行完全重建,但这是另一个问题)。一旦您让“魔鬼”跑出去,您将发现无法将其塞回去。

编辑:根据我对另一个答案的评论。

我在这里的要点是,你提出了错误的问题。关于使用getter/setter或具有公共成员的最佳实践是不存在的。唯一存在的是对于您的特定对象及其如何模拟某个具体的真实事物(或虚构的事物,也许是游戏的情况)而言,什么最好。

就我个人而言,getter/setter是两害相权取其轻的做法。因为一旦开始使用getter/setter,人们停止以批判的眼光设计对象,在公共成员中更糟糕,因为倾向于使每个东西都变成公共的。

相反,首先要检查对象的功能及其表示的含义。然后创建提供自然接口到该对象的方法。如果该自然接口涉及使用getter和setter暴露某些内部属性,那么就这样做。但是重要的是您提前考虑过并出于合理设计的原因创建了getter/setter。


4
另一个有用的区别是,您可以轻松地在成员函数上设置调试断点,但要在所有类实例化的内存位置上设置调试断点则更为困难。这开始引出一些问题,例如:我需要知道何时更改或访问此内容吗?这些数据成员之间是否存在必须维护的关系?多线程安全要求是什么? - Jason Harrison
@JasonHarrison 我理解你的观点,但是在大多数IDE中,您可以始终在公共变量上设置监视点以查看该变量的值何时发生更改。 - deepankardixit90

41

不,它们根本不是一回事。

通过不同的方式实现类接口可以实现不同级别的保护/实现隐藏:


1. 公共数据成员:

  • 提供读取和写入(如果不是const)访问数据成员的能力
  • 暴露数据对象实际存在并且在物理上是该类的成员的事实(允许创建指向该数据成员的指针类型的指针)
  • 提供左值访问数据成员的能力(允许创建对该成员的普通指针)


2. 返回对数据片段的引用的方法(可能是对私有数据成员的引用):

  • 提供读取和写入(如果不是const)访问数据的能力
  • 暴露数据对象实际存在但不暴露它在物理上是该类的成员的事实(不允许创建指向数据的指针类型的指针)
  • 提供左值访问数据的能力(允许创建对它的普通指针)


3. getter和/或setter方法(可能访问私有数据成员):

  • 提供读取和/或写入属性的能力
  • 不暴露数据对象实际存在的事实,更不用说物理上存在于该类中(不允许创建指向该数据的任何类型的指针)
  • 不提供对数据的左值访问的能力(不允许创建对它的普通指针)

getter/setter方法甚至没有暴露属性是由物理对象实现的事实。也就是说,在getter/setter对后面可能没有物理数据成员。

考虑到上述内容,看到有人声称getter和setter成对与公共数据成员相同是奇怪的。实际上,它们没有任何共同之处。

当然,每种方法都有其变化。例如,一个getter方法可能会返回对于数据的const引用,这样就处于(2)和(3)之间。


1
@AndreyT,真的很棒... 但是对于那些可能经常被检索和修改的成员,是否将getter和setter用于私有成员是一个好的实践呢? - liaK
3
它们可能不完全相同,但用途相同——都是为了让公众能够访问对象的属性。 - Mark Ransom
3
你正在从C++语言的角度看待这个问题,而我的回答则是从应用程序设计的角度来看。 - anon
1
+1 提出获取器不需要返回数据的引用或指针是正确的。能够获取副本与能够获取左值非常不同。 - tloach
1
@liaK - 这与良好的实践无关,而与你的对象模型有关。请参阅我上面的答案。你问错了问题。 - jmucchiello

26
如果您为每个数据项都有getter和setter,那么将数据设置为私有是没有意义的。这就是为什么为每个数据项都有getter和setter是一个坏主意的原因。考虑std::string类-它(可能)只有一个getter,即size()函数,根本没有setter。
或者考虑一个BankAccount对象-我们应该有SetBalance() setter来更改当前余额吗?不,大多数银行不会感谢您实现这样的功能。相反,我们需要像ApplyTransaction(Transaction & tx)这样的东西。

1
你是说我们应该避免为数据成员编写getter和setter方法吗? - liaK
2
+1 如果你想要一个界面通过“任务”/“操作”,而不是getter/setter或公共数据。 - Mark B
3
尼尔,我认为那不是一个好的建议。获取器和设置器对于Qt的属性系统至关重要。如果没有它们,就无法使用属性。此外,获取器和设置器还可以执行其他必要的操作,比如锁定互斥量、查询数据库、隐式共享、引用计数、惰性加载等等。 - CMircea
3
你的第一句话有点让我困惑。即使你有getter和setter,使数据私有仍然有意义。这似乎与你更大的观点无关,即拥有私有数据而没有getter和setter是可以的(甚至是可取的)。 - Bill the Lizard
2
@Bill 你说得对 - 那只是夸张的效果。我的观点是get/set函数的普遍糟糕。 - anon
显示剩余3条评论

14

将数据公开。如果未来确实需要在“获取器”或“设置器”中添加逻辑(这种情况可能不太可能),您可以将数据类型更改为代理类,该类重载operator=和/或operator T(其中T =您现在使用的任何类型),以实现所需的逻辑。

编辑:控制对数据的访问构成封装的想法基本上是错误的。封装是关于隐藏实现细节(通常而言!)而不是控制对数据的访问。

封装是抽象的补充:抽象处理对象的外部可见行为,而封装处理隐藏实现该行为的详细信息。

使用getter或setter实际上会减少抽象级别并公开实现-它要求客户端代码意识到该特定类将逻辑上的“数据”作为一对函数(getter和setter)实现。如上所述使用代理提供了真正的封装-除了一个晦涩的角落,它完全隐藏了逻辑上是数据的事实,实际上是由一对函数实现的。

当然,这需要保持在上下文中:对于某些类,“数据”根本不是一个好的抽象。一般来说,如果您可以提供更高级别的操作而不是数据,则更可取。尽管如此,如果最可用的抽象是读取和写入数据,则(抽象化的)数据应该像任何其他数据一样可见。获取或设置值可能涉及更多的比特复制以外的逻辑是一个实现细节,应该对用户隐藏。


2
你可以将数据类型更改为代理类 - 是的,你可以这样做,但这只是一种权宜之计,可能会带来更多麻烦。 - Mark Ransom
3
无论是 getters/setters 是否好的设计,"有一种hackish的解决方法"并不是一个论点,而是一种借口。 - BlueRaja - Danny Pflughoeft
4
@BlueRaja:胡说八道。看看修改记录。事实是,数据要么完全不可见(不公开,也没有getter或setter),要么作为数据可见,这种情况下getter和setter暴露了错误的抽象。 - Jerry Coffin
3
@Rob:简单的统计数据表明,即使是在已经维护了数十年的代码中,绝大多数的getter/setter仍然只是简单的透传,没有任何有用的功能。 - Jerry Coffin
5
@Jerry 是正确的。要么公开数据,要么将类设为抽象并将数据移至子类中。妥协方案——私有数据与公开的getter和setter——是一种hack。 - John Dibling
显示剩余6条评论

12

Getter和Setter允许您对私有成员的输入/输出应用逻辑,从而控制对数据的访问(对于了解他们的OO术语的人来说是封装)。

公共变量将类的数据公开到公众,以进行不受控制和未经验证的操纵,这几乎总是不可取的。

您还必须考虑长远的事情。您现在可能没有验证(这就是为什么公共变量似乎是一个好主意的原因),但有可能它们会在未来添加。提前添加它们可以减少重构的次数,而且这种方式验证不会破坏依赖代码。

不过要记住,并不是每个私有变量都需要自己的Getter/Setter。尼尔在他的银行示例中提出了一个很好的观点,有时Getter/Setter是没有意义的。


5
如果您确定逻辑很简单,并且在读/写变量时从不需要执行其他操作,那么最好将数据保持为公共。在C++中,我更喜欢使用结构体而不是类来强调数据是公共的事实。
然而,很多时候在访问数据成员时需要执行其他操作,或者您想要自由添加该逻辑。在这种情况下,使用getter和setter是个好主意。您的更改对代码的客户端是透明的。
一个简单的附加功能示例 - 您可能希望每次访问变量时记录调试字符串。

5
除了封装的问题(这已经足够重要了),当你使用getter/setter时,设置断点非常容易。

4
使用公共字段而不是Getter和Setter的原因包括:
  1. 不存在非法值。
  2. 客户端可以编辑它。
  3. 能够编写像object.X.Y = Z这样的内容。
  4. 承诺该值只是一个值,与其相关的没有副作用(未来也不会有)。
根据您所从事的软件类型,这些可能都是非常特殊的情况(如果您认为遇到了一个,您很可能是错误的),或者可能经常出现。这真的取决于具体情况。
(摘自 关于基于价值编程的十个问题。)

3

从严格的实用角度出发,建议您首先将所有数据成员设为私有,并将它们的getter和setter也设为私有。当您了解到整个世界(即您的“用户社区”)实际需要什么时,您可以公开适当的getter和/或setter,或编写适当受控的公共访问器。

此外(对于尼尔的益处),在调试时,有时候需要一个方便的地方来挂载调试打印和其他操作,当特定的数据成员被读取或写入时。使用getter和setter就很容易实现这一点。但如果使用公共数据成员,则非常麻烦。


在开发过程中,我发现私有的get/set函数非常有用,即使它们在公共接口中没有任何作用。 - Dennis Zickefoose

3
我认为仅仅使用getter和setter获取和设置值是没有意义的。这种方法与公共成员和私有成员之间没有区别。只有在需要控制值或认为将来可能有用时(添加一些逻辑不会使您编辑其余代码)才使用getter和setter。 作为参考,请阅读C++指南(C.131)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接