82

我目前在 Qt 和 C++ 中工作。我有具有私有数据成员和公共成员函数的类。我为类中可用的数据成员提供了公共 getter 和 setter。

现在我的问题是,如果我们的类中有数据成员的 getter 和 setter,那么将这些数据成员设为私有有什么意义呢?我同意在基类中有私有数据成员听起来合乎逻辑。但除此之外,拥有私有成员以及它们的 getter 和 setter 对我来说似乎不合逻辑。

或者相反,我们可以将所有变量设为公开,这样就完全不需要 getter 和 setter 了吗?拥有这些是一个好习惯吗?我知道拥有私有成员可以确保数据抽象,但拥有 getter 和 setter 实际上可以很容易地访问这些变量。欢迎任何有关此的指示。

4

15 回答 15

82

两者都不。你应该有做事的方法。如果其中一件事碰巧与特定的内部变量相对应,那很好,但应该没有任何东西可以将这一点传达给您班级的用户。

私有数据是私有的,因此您可以随时替换实现(并且可以进行完全重建,但这是一个不同的问题)。一旦你把精灵从瓶子里拿出来,你会发现不可能把它推回去。

编辑:在我对另一个答案发表评论之后。

我的意思是你问错了问题。没有关于使用 getter/setter 或拥有公共成员的最佳实践。只有最适合您的特定对象的内容以及它如何模拟某些特定的现实世界事物(或在游戏中可能是想象的事物)。

就个人而言,getter/setter 是两种弊端中的较小者。因为一旦你开始制作 getter/setter,人们就会停止以批判的眼光来设计对象,哪些数据应该可见,哪些数据不应该可见。对于公共成员来说,情况更糟,因为趋势变得公开一切。

相反,检查对象的作用以及作为该对象的某物意味着什么。然后创建为该对象提供自然接口的方法。自然接口涉及使用 getter 和 setter 公开一些内部属性,就这样吧。但重要的部分是您提前考虑了它并出于设计合理的原因创建了 getter/setter。

于 2010-06-04T19:13:34.310 回答
39

不,它甚至完全不是一回事。

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


1.公共数据成员:

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


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

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


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

  • 提供对属性的读取和/或写入访问权限
  • 不公开数据对象物理存在的事实,更不用说物理存在于此类中(不允许创建指向该数据的指向成员类型的指针,或任何类型的指针)
  • 不提供对数据的左值访问(不允许创建指向它的普通指针)

getter/setter 方法甚至没有公开属性由物理对象实现的事实。即在getter/setter 对后面可能没有物理数据成员。

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

当然,每种方法都有不同的变化。例如,getter 方法可能会返回对数据的 const 引用,这会将其置于 (2) 和 (3) 之间的某个位置。

于 2010-06-04T19:29:11.120 回答
25

如果每个数据项都有 getter 和 setter,那么将数据设为私有是没有意义的。这就是为什么为每个数据项设置 getter 和 setter 是一个坏主意。考虑 std::string 类——它(可能)有一个 getter、size() 函数,根本没有 setter。

或者考虑一个BankAccount对象——我们是否应该有 SetBalance()setter 来改变当前的余额?不,大多数银行不会感谢您实施这样的事情。相反,我们想要类似ApplyTransaction( Transaction & tx ).

于 2010-06-04T19:10:20.103 回答
11

Getter 和 Setter 让您可以将逻辑应用于私有成员的输入/输出,从而控制对数据的访问(对了解其 OO 术语的人的封装)。

公共变量使您的班级数据向公众开放,以进行不受控制和未经验证的操作,这几乎总是不受欢迎的。

您还必须长期考虑这些事情。您现在可能没有验证(这就是公共变量似乎是个好主意的原因),但它们有可能会被添加。提前添加它们会留下框架,因此减少了对 raod 的重构,更不用说验证不会以这种方式破坏依赖代码)。

但是请记住,这并不意味着每个私有变量都需要自己的 getter/setter。Neil 在他的银行示例中提出了一个很好的观点,即有时 Getters/Setters 是没有意义的。

于 2010-06-04T19:09:58.047 回答
11

公开数据。如果有一天您确实需要“getter”或“setter”中的逻辑(不太可能),您可以将数据类型更改为重载operator=和/或operator T(其中 T=您现在使用的任何类型)的代理类实现必要的逻辑。

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

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

使用 getter 或 setter 实际上降低了抽象级别并公开了实现——它要求客户端代码意识到这个特定的类将逻辑上的“数据”实现为一对函数(getter 和 setter)。使用我上面建议的代理提供了真正的封装——除了一个晦涩的极端情况,它完全隐藏了这样一个事实,即逻辑上是一段数据实际上是通过一对函数实现的。

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

于 2010-06-04T19:13:13.920 回答
5

如果您非常确定您的逻辑很简单,并且在读取/写入变量时不需要做其他事情,那么最好将数据公开。在 C++ 案例中,我更喜欢使用 struct 而不是 class 来强调数据是公共的这一事实。

但是,在访问数据成员时,您经常需要做一些其他事情,或者您想给自己稍后添加此逻辑的自由。在这种情况下,getter 和 setter 是个好主意。您的更改将对您的代码的客户透明。

附加功能的一个简单示例 - 您可能希望在每次访问变量时记录一个调试字符串。

于 2010-06-04T19:16:44.957 回答
5

除了封装问题(这是有充分理由的)之外,当你有 getter/setter 时,只要设置/访问变量就很容易设置断点。

于 2010-06-04T19:18:14.267 回答
4

使用公共字段而不是 getter 和 setter 的原因包括:

  1. 没有非法值。
  2. 预计客户将对其进行编辑。
  3. 能够写出object.XY = Z之类的东西。
  4. 做出强有力的承诺,即价值只是一个价值,并且没有与之相关的副作用(将来也不会)。

根据您使用的软件类型,这些可能都是非常特殊的情况(如果您认为遇到了一种情况,那么您可能错了)或者它们可能一直都在发生。这真的取决于。

(来自基于价值的编程的十个问题。)

于 2015-06-25T14:07:14.513 回答
3

在严格实用的基础上,我建议您首先将所有数据成员设为私有,并将它们的 getter 和 setter 设为私有。当您发现世界其他地方(即您的“(l)用户社区”)实际需要什么时,您可以公开适当的 getter 和/或 setter,或编写适当控制的公共访问器。

此外(为了 Neil 的利益),在调试期间,有时在读取或写入特定数据成员时有一个方便的地方来挂起调试打印和其他操作是有用的。使用 getter 和 setter,这很容易。对于公共数据成员来说,这是一个巨大的痛苦。

于 2010-06-04T19:16:06.783 回答
2

我一直认为 getter 和 setter 在大多数编程语言中是故意冗长的,特别是为了让你在使用它们时三思而后行——为什么你的调用者需要知道你的类的内部工作应该是你脑海中的问题.

于 2010-06-04T19:27:33.953 回答
2

我相信仅仅使用 getter 和 setter 来获取和设置值是没有用的。使用这种方法的公共成员和私有成员之间没有区别。仅当您需要以某种方式控制值或您认为它在将来可能有用时才使用 getter 和 setter(添加一些逻辑不会让您编辑其余代码)。

作为参考,请阅读 C++ 指南 (C.131)

于 2017-10-10T16:52:28.023 回答
1

我建议您没有公共数据成员(POD 结构除外)。我也不建议您为所有数据成员使用 getter 和 setter。相反,为您的类定义一个干净的公共接口。这可能包括获取和/或设置属性值的方法,并且这些属性可以实现为成员变量。但不要为所有成员创建 getter 和 setter。

这个想法是您将接口与实现分开,允许您修改实现而无需类的用户更改他们的代码。如果您通过 getter 和 setter 公开所有内容,那么与使用公共数据相比,您没有任何改进。

于 2010-06-04T19:14:36.317 回答
1

使用 getter 和 setter 将允许您修改将值​​提供给用户的方式。

考虑以下:

double premium;
double tax;

然后,您使用此值在各处编写代码premium以获得溢价:

double myPremium = class.premium;

您的规格刚刚改变,从用户的角度来看,溢价需要premium + tax

您必须premium在代码中使用该值的任何地方进行修改,并添加tax到其中。

相反,如果您这样实现它:

double premium;
double tax;

double GetPremium(){return premium;};

您的所有代码都将使用GetPremium(),您的tax更改将是一行:

double premium;
double tax;

double GetPremium(){return premium + tax;};
于 2010-06-04T19:33:33.237 回答
0

返回值也会影响 getter 和 setter 的使用。获取变量的值或访问私有数据成员变量是不同的。按值保持完整性,按引用或按指针不是那么多。

于 2010-06-04T19:15:59.340 回答
0

Getter 和 Setter 的存在主要是为了让我们可以控制如何获取成员以及如何设置它们。Getter 和 setter 不只是作为访问特定成员的一种方式存在,而是为了确保在我们尝试设置成员之前,它可能满足某些条件,或者如果我们获取它,我们可以控制我们返回该成员的副本非原始类型的成员。总体而言,当您想要管道如何与数据成员进行交互时,您应该尝试使用 g/s'ers,没有它们会导致成员以临时方式使用。

于 2014-10-27T19:28:31.857 回答