为什么我们需要私有变量?


210

为什么在类中需要私有变量?

我读过的每本关于编程的书都说这是一个私有变量,这是您定义它的方式,但仅此而已。

在我看来,这些解释的措辞总是像我们对我们的职业确实存在信任危机。这些解释听起来总是像其他程序员要弄乱我们的代码一样。但是,有许多编程语言没有私有变量。

  1. 私有变量可以帮助防止什么?

  2. 您如何确定特定财产是否应为私有?如果默认情况下每个字段都应该是私有的,那么为什么在类中有公共数据成员呢?

  3. 在什么情况下应该公开变量?


9
您的问题听起来与语言无关,但是您已将其标记为Java和C ++。如果您对这两种语言之外的观点感兴趣,则可能应该这样说。
詹姆斯

8
根据记录,创建私有方法不会增加“安全性”。应用程序的其他部分仍可以使用反射访问私有成员。
kba 2012年

3
私有变量和公共变量的用法和区别以及我们为什么拥有它们,只能在通用的面向对象原理的上下文中才能理解。不能单独理解。您可能需要从这个角度来看它,事情可能会变得更加清晰。
Nazar Merza


3
如果您抓住任何在车上系上安全带的人,则应该拿走他们的驾驶执照,因为他们显然不信任自己的驾驶能力。
gnasher729

Answers:


308

与其说是信任,不如说是管理复杂性的问题。

可以从班级外部访问公共成员,出于实际考虑,这意味着“可能在任何地方”。如果某个公共领域出现问题,罪魁祸首可能在任何地方,因此为了查明该错误,您可能必须查看大量代码。

相比之下,私有成员只能从同一类内部访问,因此,如果出现问题,通常只需要查看一个源文件。如果您的项目中有一百万行代码,但是您的类很小,那么可以将错误跟踪的工作量减少1000倍。

另一个优点与“耦合”的概念有关。由另一个类使用m的一个类的公共成员引入了一个依赖关系:如果更改in ,则还必须检查in的用法。更糟糕的是,类中没有任何内容可以告诉您在哪里使用了代码,因此您必须再次搜索整个代码库。如果这是您正在编写的库,则甚至必须确保外部代码ABmAmBAm您的项目不会因为您的更改而中断。在实践中,无论多么痛苦,库都倾向于尽可能长时间地保留其原始方法签名,然后在主要版本更新中引入一些重大更改。相比之下,使用私有成员,您可以立即排除依赖项-无法从外部访问它们,因此所有依赖项都包含在类内部。

在这种情况下,“其他程序员”包括您的未来和过去。有可能到头来你知道现在,你不应该用你的变量Y做这个事情X,但你一定已经忘记了三个月下来,当客户急需你实现一些功能的道路,你想知道为什么这样做X休息Y以晦涩的方式出现。

因此,关于何时应将其设为私有:我会说默认情况下将所有内容设为私有,然后仅公开那些绝对必须公开的部分。您可以将更多信息设为私有,那就更好了。


24
+1是解决问题的根源,尽管我个人发现我在停止循环时更易于维护代码,以确保经常使用的变量保持私有。当然,将来可能会出现错误,但是要筛选的代码行少了60条,引导的变量和函数也少了;)
Jeffrey Sweeney 2012年

48
现代计算的圣杯正在降低复杂性。

1
我总是喜欢说调试的一部分不仅是直接朝着“出了什么问题”迈进,还包括绕过找出“没出什么问题”的回旋过程,并将研究重点放在剩下的问题上。如果我可以让编译器使用诸如私有变量之类的工具来帮助我定义“不可能发生的事情”,那么可以节省大量时间。仅在极少数情况下,我证明发生了错误的情况是不可能发生的,我才必须挖掘更可怕的假设,例如缓冲区溢出可能会覆盖我的私有数据。
Cort Ammon

2
就是这样 当人们听到“信息隐藏”,他们认为他们应该使用私有变量的东西像加密密钥,数据库凭据等
韦恩沃纳

2
@AdamZerner看到“告诉,不要问”(martinfowler.com/bliki/TellDontAsk.html)有关首先使用getter / setter的信息-否则,是的,因为您应该可以随时更改值的内部表示形式你希望。和往常一样,也有例外...(例如DTO)
doubleYou

111

私有变量有助于防止人们依赖代码的某些部分。例如,假设您要实现一些数据结构。您希望数据结构的用户不在乎如何实现它,而只是通过定义良好的界面使用实现。原因是,如果没有人依赖于您的实现,则可以随时更改它。例如,您可以更改后端实现以提高性能。依赖于您的实现的任何其他开发人员都会崩溃,而界面用户会没事的。拥有在不影响类用户的情况下更改实现的灵活性,这是使用私有变量(更广泛地说,是封装)可为您带来的巨大好处。

另外,这并不是真正的“信任危机”。如果公开一条数据,则不能确保没有人依赖它。依赖某些特定于实现的变量通常非常方便,而不是通过公共接口,尤其是在截止期限之内。此外,开发人员不会总是意识到自己依赖于可能会发生变化的事物。

我希望这样回答您的其他问题。您的所有实现细节都应该是私有的,而公共部分应该是一个小巧,简洁,定义明确的接口,以供您使用类。


24
IMO也是关于lib作者和lib使用者之间的通信。公共接口就像“使用这个”,而私有接口就像“不使用那个”,除了在Java中,如果真的将它设为私有,那么您真的不能偶然使用它,这样更加安全和清晰。
chakrit

5
@chakrit和其他人:请注意,那些真正想使用您的私有字段和方法的人可以这样做(通过反射)。private小于“并非主要用于安全性”,它没有提供任何“安全性”。这只是一个很强的暗示(通过编译器提供),提示不要访问它。

@delnan请注意“偶然”这句话,也许我对此应该更清楚。
chakrit 2012年

@chakrit是的,我并不是要暗示您错了。我只是想宣传这一点。

1
@delnan:私有字段确实以某些语言提供某些形式的安全性。例如,请参见Eric Lippert的答案:访问级别和修饰符(私有,密封等)是否出于C#的安全目的?
布莱恩

25

此处的关键字是Encapsulation。在OOP中,您想使用私有变量来强制对对象/类进行正确的封装。

尽管其他程序员并没有吸引您,但他们确实与您的代码进行了交互。如果您不将变量设为私有,则他们很可能会在自己的代码中引用该变量,而不会造成任何伤害。但是,如果您需要回到班级并进行一些更改,则不再需要知道谁在哪个位置使用哪个变量。封装的目的是使类的外部接口显式,以便您仅知道这些(通常)方法可以被其他方法使用。

  1. 因此,私有变量确保相应的变量仅保留在定义类中。如果您需要更改,则更改是该类本身的本地更改。

  2. 在像C ++或Java这样的传统语言中,通常会将所有内容设为私有,并且只能由相应的getter和setter进行访问。无需真正决定。

  3. 有时,f.ex。在C ++结构中,您只需要一个类即可将多种事物组合在一起。例如,考虑一个Vector类,它仅具有xy属性。在这些情况下,您可以通过将它们声明为公共属性来允许直接访问这些属性。特别是,您不必在意外部的某些代码是否通过直接将新值写入x或来修改类的对象y

另外,请注意,在其他语言中,此问题略有不同。例如,扎根于功能编程的语言强调数据的不变性,即数据值根本无法更改。在这种情况下,您不必关心其他程序员(或更确切地说是他们的代码)对数据的处理方式。他们根本无法做任何可能影响您代码的事情,因为所有数据都是不可变的。

因此,在这些语言中,您会得到一种称为统一访问原则的知识,该原则不故意区分诸如getter和setter之类的方法,而是提供对变量/函数的直接访问。因此,您可以说私有声明在面向对象的场景中非常流行。

这也说明了如何扩展知识以包括其他领域,可以使您以全新的方式查看现有概念。


1
+1“虽然其他程序员并没有吸引您,但他们确实会与您的代码进行交互。” 程序员使用您的代码并不是天生的邪恶。通过使用私有变量,您可以确保它们(以及您将来)不会受到使用代码的伤害。它还可以帮助您设计更好的API并记录下来。
2013年

7

私有变量可确保以后在对象或函数范围之外的引用不会无意中影响其他变量。对于大型编程项目,这可以帮助避免很多有趣的问题(通常不会被编译器捕获)。

例如,像Javascript这样的原型语言可以允许用户在他们认为合适的地方粘贴变量:

function SomeObject() {
    this.a = 2; //Public (well, actually protected) variable

    this.showA = function() {
        alert(this.a);
    }
}

//Some lines down...

var obj = new SomeObject();
obj.a = 3;
obj.showA(); //Will alert 3. Uh oh!

如果该变量是私有变量,则无法从外部进行更改:

function SomeObject() {
    var a = 2; //Private variable

    this.showA = function() {
        alert(a);
    }
}

//Some lines down...

var obj = new SomeObject();
obj.a = 3;
obj.showA(); //Will alert 2

也就是说,并非所有变量都必须是私有的,过多使用私有变量实际上会使代码更麻烦。不会严重影响对象的琐碎字段不需要get()set()方法。

私有变量还使将来更容易扩展代码,而不必依赖繁琐的文档来了解许多公共变量的作用。如果可以覆盖较少的变量,则意外破坏功能的风险较小。

当对象/函数将要访问其他对象/函数时,应使用公共变量,因为通常,私有变量不能这样做。您可能有从几个到相当多个公共变量的任何地方,如果正确使用它们,使用它们也不是一件坏事。


警报3为何会是“哦,哦!”的原因并不明显。在这种情况下。也许这就是程序员想要发生的事情。
凌晨

@immibis也许,也许我应该在答案中详细说明。但是,访问属性可以为违反某些OOP原则(如开闭原则或LoD)敞开大门,因为您可能会暴露实现细节。根据您的观点,这取决于房地产的确切身份。大多数语言都将对象用作引用,并且当对象的引用被传递并且同伴开发者悄悄地更改属性时,调试起来真的很难。IMO如果您有一个类实例,则使用方法会更轻松,更可扩展。
Jeffrey Sweeney

7

有一些很好的答案指出了类的未来维护者和用户的利益。但是,原始设计也有一些好处。它提供了一个清晰的分界点,介于您的目标是使您自己的事情变得容易的时候,还是您的目标是使类用户的事情变得容易的时候。当您在公共和私人之间进行选择时,它会发出信号通知您的大脑切换到一种或另一种模式,最终您会获得更好的API。

不是说没有隐私的语言会使这种设计变得不可能,只是可能性很小。foo.getBar()人们倾向于提示没有必要写类似getter的东西,因为它更容易编写foo.bar,而不是被提示写类似的东西,但是当您以后遇到更长的怪兽(例如)时,就不会重新考虑该决定obj.container[baz].foo.bar。从未使用过更严格的语言工作的程序员甚至可能看不出这是怎么回事。

这就是为什么在没有隐私的语言中,人们通常会采用模仿它的命名标准,例如在所有“私有”成员前添加下划线。即使该语言没有强制执行,它也是一个有用的信号。


3

除非绝对不需要将所有变量公开,否则所有变量都应为私有变量(几乎永远不会,您应该使用属性/获取器和设置器)。

变量在很大程度上提供了对象的状态,而私有变量则阻止其他变量进入并更改对象的状态。


5
对主题的看法过于狭narrow。在某些情况下,甚至甚至更喜欢公共场所的访问权限,这是必要的
Roland Tepp

当然可以设计状态不能更改的对象(不可变对象),但这不是适用于所有类的一般规则。以我的经验,大多数对象使其他人很容易更改对象的状态。
David K

1
@DavidK也许bbb的意思是“私有变量阻止其他人进入并随意改变对象的状态。” 公共方法可以更改对象的私有状态,但只能以对对象功能必需且一致的方式进行。
Max Nanasy 2014年

@Max Nanasy:是的,可以控制如何当一个只提供公共接口方法的状态被改变。例如,为了使对象“自洽”,可能必须满足成员之间的关系,并且可以允许将自洽状态仅更改为另一个自洽状态。如果所有这些变量都是公共的,则您不能强制执行此操作。但是也可能有一些您不希望进行任何更改的对象,因此弄清楚我们正在谈论的是哪些是很重要的。
David K

2
  • 私有变量可以帮助防止什么?

这是要弄清楚哪些属性和方法是接口,哪些实际上是核心功能。公共方法/属性是关于其他代码的,通常是其他使用您的对象的开发人员的代码。我从未在同一项目中由100多人组成的团队工作过,但是根据我的经验,也许是由3-5人组成的团队以及多达20个其他开发人员使用我编写的内容,其他问题似乎很愚蠢。

注意:我主要是JavaScript开发人员。我通常不担心其他开发人员查看我的代码,并且我充分意识到他们可以随时重新定义我的内容。我认为他们有足够的能力先知道他们在做什么。如果没有,我不太可能在一个不使用源代码控制的团队中工作。

  • 您如何确定一组特定的属性是否应为私有?如果默认情况下每个字段都应该是私有的,那么为什么在类中有公共数据成员呢?

我以前认为,当您实际上没有对要设置的属性进行任何形式的验证或其他特殊处理时,将getter和setter放在私有属性上是愚蠢的。我仍然认为并非总是必要的,但是当您处理大规模,高复杂性时,但最重要的是,许多其他开发人员都以一致的方式依赖于您的对象,因此以一致和统一的方式。当人们看到所谓的方法getThatsetThat,他们确切地知道意图是什么,并且可以编写对象与您的对象进行交互,并期望他们总是得到西红柿而不是tomahtos。如果他们不这样做,他们就知道您的对象正在提供可能不应该给他们的东西,这意味着它允许其他对象对可能不应该使用的数据做一些事情。您可以根据需要进行控制。

另一个重要的优点是,根据某种变化的状态上下文,使对象的传递或解释与getter或setter不同的值更容易。因为他们使用一种方法来访问您的东西,所以在不破坏依赖于该对象的其他代码的情况下,更改对象的操作方式要容易得多。重要的原则是,只有您的对象实际上会更改您使它负责的数据。这对于使代码保持松散耦合特别重要,这在可移植性和易于修改方面是一个巨大的胜利。

  • 在什么情况下应该公开变量?

对于一流的函数(您可以将函数作为参数传递),除了在较小规模的项目中不必这样做,我没有想到很多重要的原因。如果没有它,我想您可能会有一些成员被其他对象大量且定期地处理,以至于不断调用get和set来处理实际上并没有真正接触数据本身的对象似乎有点麻烦。本身会让我想知道为什么对象负责该数据。一般来说,在决定是否需要公共数据属性之前,我倾向于希望重新检查体系结构中的缺陷。IMO,一种让您执行通常是个坏主意的语言没有错。有些语言不同意。


恕我直言,公开类的唯一目的就是公开公共变量,这没有什么特别的错误。事实上,JVM具有用于该目的的许多内置的类:int[]double[]Object[]等一应,但是,必须非常小心如何一个公开这样一个类的实例。void CopyLocationTo(Point p);[接受Point来自呼叫者的a] 的含义比清楚Point location(),因为尚不清楚Point pt = foo.location(); pt.x ++;对的位置会有什么影响foo
2013年

2

有关SO的相关问题,请参阅我的这篇文章

简而言之,变量作用域使您可以向代码使用者显示应该和不应该使用的内容。私有变量可以保存通过使用属性设置器或处理方法已“审核”的数据,以确保整个对象处于一致状态。直接更改私有变量的值可能会导致对象变得不一致。通过使其私有化,某人必须努力工作并具有很高的运行时权限才能进行更改。

因此,正确的成员范围是自文档代码的关键。


2

我将使用一个真实的例子从另一个角度来讨论封装的价值。相当早之前(80年代初),为Radio Shack彩色计算机编写了一款名为Daggorath的地下城游戏。几年前,Richard Hunerlach将其从纸面汇编器列表移植到C / C ++(http://mspencer.net/daggorath/dodpcp.html)。一段时间后,我得到了代码,并开始对其进行重构以提供更好的封装(http://dbp-consulting.com/stuff/)。

该代码已经被分解到不同的对象中以处理调度,视频,用户输入,地牢创建,怪物等,但是您绝对可以确定它是从汇编程序移植的。我最大的任务是增加封装。我的意思是要获得代码的不同部分,以使彼此的业务脱颖而出。例如,有很多地方可以直接更改视频变量以产生一些效果。有些人通过错误检查来做到这一点,有些则没有,对改变事物的含义有不同的想法。有很多重复的代码。相反,视频部分需要具有一个界面来完成所需的任务,并且执行该操作的代码可以全部集中在一个地方,一个想法,一个地方进行调试。那种事情很猖ramp。

随着代码开始变得捉襟见肘,甚至未被诊断出的错误也消失了。代码变得更高的质量。每个任务都变成了代码中一个地方的责任,只有一个地方可以解决。(每次我再次通过代码时,我仍然能找到更多。)

关于代码的所有可见内容就是您的界面。无论您是真的还是不是真的。如果您尽可能限制可见性,那么以后就不会将代码放在错误的位置。可以清楚地看出代码的哪一部分负责哪些数据。它使设计更好,更清洁,更简单,更优雅。

如果您使一个对象负责其自己的数据,并为需要发生某些事情的其他所有人提供接口,则代码将变得更加简单。它只是掉出来。您有只做一件事的小型简单例程。更少的复杂性=更少的错误。


在“我们的lib用户的自由”与“给我们减少麻烦”之间的硬平衡。从某种意义上讲,我会开始相信封装会更好,因为这样可以防止我自己的编码出现错误,并且我还将帮助最终用户防止出现错误。但是缺乏自由可能会简单地将它们抛弃,寻找替代方案……可能尚未使用编码,但是通过公共方法可以预见的功能可以帮助平衡,但需要更多时间。
Aquarius Power

太糟糕了,重构的版本不在github上
。...– jmoreno

2

它与信任或对攻击的恐惧无关,仅与封装有关,而不是在类的用户上强制使用不必要的信息。

考虑私有常量-它们不应该包含秘密值(那些值应该存储在其他地方),不能更改,不需要将其传递到您的类中(否则它们必须是公共的)。它们的唯一可能用途是在OTHER类中作为常量。但是,如果您这样做,那么这些类现在将取决于您的类,以进行与您的类无关的工作。如果更改常数,其他类可能会中断。这对双方都是不利的-作为班级的作家,您希望自由地尽可能地改变,并且不想担心超出您控制范围的事情。类的使用者希望能够依赖于类的公开细节,而不必担心您对其进行更改并破坏他们的代码。

班级的使用者想知道与班级进行交互所需要的一切,并且不想要任何关于班级的知识,而这不会改变他们的操作方式:这是无用的琐事。如果您使用了带反射的语言,那么您有多少次使用它来学习不是某个类是如何做的,也不是在何处意外地抛出异常,而是仅学习私有字段和方法的名称?我打赌永远不会。因为您没有使用该知识。


1

OOP概念具有继承性,它具有其功能之一(Java或C ++)。因此,如果我们要继承(意味着要访问继承的类的变量),则有可能影响这些变量。因此,我们必须确定变量是否可以更改。

仅出于此目的,我们在OOP中使用访问修饰符。修饰符之一是私有的,这意味着只能由该类访问。任何其他类都不能影响这些变量。

众所周知,protected意味着将要继承的类可以访问它。

由于这些原因,OOP中为什么会有修饰符概念(任何类都可以使用OOP概念访问其他类变量)。如果没有修饰符概念,则意味着在继承类或使用其他OOP概念时会很困难。


1

正如其他人所指出的那样,私有变量可以避免误操作导致对象进入不一致状态,并且难以跟踪错误和无法预料的异常。

但是,另一方面,其他方面最常被忽略的是受保护的字段。

扩展子类将具有对受保护字段的完全访问权限,从而使对象像此类字段是公共的一样易碎,但是该脆弱性仅限于扩展类自身(除非它进一步暴露此类字段)。

因此,很难将公共字段视为好字段,迄今为止,使用公共字段的唯一原因是将其用作配置参数类(一个非常简单的类,具有许多字段,没有逻辑,因此该类仅作为参数传递给一些方法)。

但另一方面,私有字段降低了代码对其他用户的灵活性。

灵活性与麻烦,利弊:

由您的代码在vanilla类中使用受保护的字段实例化的对象是安全的,并且由您自己负责。

另一方面,由代码的用户实例化的,具有受保护字段的类扩展对象的类是他们的责任,而不是您的责任。

因此,没有充分记录的受保护字段/方法,或者如果用户不能真正理解应如何使用这些字段和方法,则很有可能给自己和您造成不必要的麻烦。

另一方面,将大多数事物设为私有将降低用户的灵活性,甚至可能使他们放弃寻找可维护的替代方案,因为他们可能不想创建和维护分支只是为了让事情按自己的方式进行。

因此,真正重要的是在私有,受保护和公共之间取得良好的平衡。

现在,在私有和受保护之间做出决定是真正的问题。

什么时候使用受保护的?

每当您了解某个字段具有很高的灵活性时,都应将其编码为受保护的字段。灵活性是:从变为null(始终检查null并将其识别为有效状态而不抛出异常),再到由类ex使用之前具有约束。> = 0,<100等,并自动修复上溢/下溢值,最多引发一条警告消息。

因此,对于这样一个受保护的字段,您可以创建一个getter并仅使用它(而不是直接使用field变量),而其他用户可能不使用它,以防他们希望对自己的特定代码有更大的灵活性,例如:如果他们希望负值在扩展类中可以正常工作。


-1

imo @tdammers是不正确的,实际上是一种误导。

存在私有变量以隐藏实现细节。您的班级A可能正在使用array来存储分数。明天您可能要使用treepriority queue代替。班上所有用户需要的是输入分数和名称addScore()的方法,以及找出谁是前10 名的方法getTop(n)

他们不需要访问基础数据结构。听起来不错吗?好吧,有一些警告。

无论如何,您不应该存储很多状态,大多数状态都是不需要的。即使将状态“隔离”到特定的类中,存储状态也会使您的代码复杂化。考虑一下,私有变量可能已更改,因为另一个对象称为该类的方法。您仍然需要弄清楚该方法的调用时间和位置,并且理论上可以在任何地方调用该公共方法。

您可能要做的最好的事情是限制程序包含的状态数量,并在可能的情况下使用纯函数。


-1
  • 访问变量意味着在程序运行时访问其值,使变量私有将在代码运行时保护其值。
  • 所谓“数据隐藏”的目的是使内部数据对使用该类的其他类隐藏。这些其他类仅应通过在类上调用方法来访问行为,而不应直接更改变量的值。
  • 通过使变量成为私有数据成员,您可以更轻松地确保永远不会修改或更改该值。另一方面,如果变量是public,则另一个类可以修改或更改该值,这可能导致代码的其他部分崩溃。

1
这似乎并没有提供任何关于之前17个答案中提出和解释的要点的实质内容

-4

您首先应该了解面向对象编程的概念。它具有抽象,封装等。

抽象-您无需了解实现的下划线细节就可以了解逻辑。

封装-您看不到对象的下划线实现。只有您可以看到该对象的公共接口。

现在,在使用面向对象的程序(例如C#,Java,C ++)之一的特定实现中,您可以实现这些概念。

私有-应当对外界隐藏的实现。这样您就可以更改它,并且该类的用户不会受到影响。

公共-这是一个可以使用您的对象的接口。


1
protected-受子类(扩展程序)(java)直接访问。
Aquarius Power
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.