私有与受保护 - 可见性良好实践问题 [已关闭]

Private vs Protected - Visibility Good-Practice Concern [closed]

提问人:Madara's Ghost 提问时间:12/2/2011 最后编辑:Madara's Ghost 更新时间:12/20/2018 访问量:146430

问:


想改进这个问题吗?更新问题,以便可以通过编辑这篇文章用事实和引文来回答。

6年前关闭。

我一直在寻找,我知道理论上的区别。

  • public - 任何类/函数都可以访问方法/属性。
  • protected - 只有此类和任何子类才能访问方法/属性。
  • private - 只有此类可以访问方法/属性。它甚至不会被继承。

这一切都很好,问题是,它们之间有什么实际区别?你什么时候使用,什么时候使用?是否有标准或可接受的良好做法?privateprotected

到目前为止,为了保留继承和多态性的概念,我用于任何应该从外部访问的东西(如构造函数和主类功能),以及用于内部方法(逻辑、帮助程序方法等)。我走在正确的轨道上吗?publicprotected

(请注意,这个问题是给我的,但也供将来参考,因为我还没有见过这样的问题)。

OOP 与语言无关的 编码风格

评论

34赞 Madara's Ghost 12/2/2011
这有关系吗?任何支持 OOP 的语言都有这个问题。我碰巧用PHP编程,但我认为这个问题适用于任何OOP支持语言。
2赞 Paul Bellora 12/2/2011
好吧,很公平,只是想知道你是否忘记了标记。现在我看到了 oop 标签。
0赞 user4271704 10/3/2016
我必须为类的每个属性设置可见性(私有/公共/受保护)吗?或者只有其中一些需要具有可见性类型?如果是,如何决定哪个属性需要将可见性设置为类之首?
1赞 iPherian 10/23/2016
顺便说一句,受保护和私有之间的区别似乎很明显。如果子类将使用方法/变量,则使用 protected,否则使用 private。具体来说,如果子类必须在父类中重新定义一个非常相似的私有变量,只需将其保护即可。
0赞 RBT 2/4/2017
C# 语言中还有另一个访问说明符,用于限制物理程序集中类或其成员的访问级别。不过,我不确定它的支持或其他语言中的类似内容。internal

答:

150赞 JB Nizet 12/2/2011 #1

不,你没有走在正确的轨道上。一个好的经验法则是:让一切尽可能私密。这使您的类更加封装,并允许更改类的内部结构,而不会影响使用类的代码。

如果你把你的类设计成可继承的,那么请仔细选择可以从子类中覆盖和访问的内容,并使其受到保护(最后,如果你想让它可访问但不可覆盖,就说 Java)。但请注意,一旦您接受类的子类,并且存在受保护的字段或方法,该字段或方法就是该类的公共 API 的一部分,以后可能不会在不破坏子类的情况下进行更改。

一个不打算被继承的类应该是最终的(在 Java 中)。为了进行单元测试,您可以放宽一些访问规则(从私有到受保护,从最终到非最终),但随后将其记录下来,并明确指出尽管该方法受到保护,但不应重写它。

评论

2赞 Madara's Ghost 12/2/2011
这里真正的问题是关于我什么时候想要继承财产,什么时候不继承?我真的不知道将来某个用户是否想参加我的课程并扩展它......privateprotected
18赞 Thorsten Dittmar 12/2/2011
好吧,问题不在于用户想要覆盖什么,而在于您想要允许覆盖什么。通常,转换方向并尝试思考会有所帮助:如果我使用该类并创建了一个子类,我希望能够覆盖什么?有时其他用户仍然会错过一些东西......
3赞 RBT 2/4/2017
受保护的字段是继承中的不良做法!请小心它。原因如下
6赞 Larry 11/4/2017
私有字段在继承实践中是不好的!(过度夸大其词)请阅读我的回答。
13赞 bruno desthuilliers 11/15/2017
典型的控制狂观点。
7赞 Alok Save 12/2/2011 #2

何时使用私有,何时使用受保护

私有继承可以被认为是从关系而不是 IS-A 关系的角度来实现的。简单地说,继承类的外部接口与继承的类没有(可见的)关系,它仅使用继承来实现基类提供的类似功能。private

与私有继承不同,受保护的继承是继承的一种受限制形式,其中派生类是基类的一种,它希望将派生成员的访问限制为仅对派生类的访问。

评论

2赞 shawnhcorey 1/5/2015
“在关系方面实施”是一种 HAS-A 关系。如果所有属性都是私有的,则访问它们的唯一方法是通过方法。这与子类包含超类的对象是一回事。从具体类中消除所有受保护的属性也意味着从它们中消除所有继承。
2赞 RBT 2/4/2017
有趣的思维过程 - 私人继承。@shawnhcorey感谢您明确表示它指向 HAS-A 关系,即关联(可以是聚合或组合)。我觉得这篇文章也应该更新以澄清这一点。
16赞 Anurag 12/2/2011 #3

我前段时间读了一篇文章,谈到了尽可能地锁定每个班级。使所有内容成为最终的和私密的,除非您立即需要向外界公开某些数据或功能。将范围扩大到以后更允许的范围总是很容易的,但反过来就不行了。首先考虑制作尽可能多的东西,这将使选择变得更加容易。finalprivateprotected

  1. 将所有类设置为最终类,除非您需要立即对它们进行子类化。
  2. 使所有方法成为最终方法,除非您需要立即子类化并重写它们。
  3. 使所有方法参数成为最终参数,除非您需要在方法主体中更改它们,否则大多数时候这有点尴尬。

现在,如果你只剩下最后一堂课,那么就把所有东西都设为私有,除非世界绝对需要什么——把它公之于众。

如果剩下的类确实有子类,请仔细检查每个属性和方法。首先考虑是否要向子类公开该属性/方法。如果这样做,请考虑如果子类在重写过程中弄乱了属性值或方法实现,它是否会对对象造成严重破坏。如果可能的话,并且你想保护你的类的属性/方法,甚至免受子类的影响(听起来很讽刺,我知道),那么就把它设为私有。否则,请使其受到保护。

免责声明:我不太会用 Java :)编程

评论

2赞 M.Stramm 12/3/2015
澄清一下:Java 中的 A 是一个不能继承的类。A 是非虚拟的,即不能被子类覆盖(默认情况下 Java 方法是虚拟的)。A 是一个不可变的变量引用(从某种意义上说,它在初始化后无法修改)。final classfinal methodfinal field/parameter/variable
1赞 GordonM 8/8/2017
有趣的是,我看到了完全相反的建议,除非确定覆盖所讨论的事物有可能破坏不变性,否则没有什么应该是最终的。这样,任何人都可以根据需要自由扩展您的课程。
0赞 user949300 8/24/2017
你能说出一个案例吗,在你的编程生涯中,将方法参数标记为“final”对你的理解产生了影响?(编译器要求的情况除外)
1赞 ujwal dhakal 2/20/2016 #4

好吧,这完全是关于封装的,如果 paybill 类处理付款的计费,那么在产品类中,为什么它需要计费过程的整个过程,即付款方式、如何付款、在哪里付款......因此,只允许用于其他类和对象的内容,只允许其他类也使用它们,只允许其他类和对象使用,并且只对扩展类的限制受到保护。因为你是宇智波斑,私人就像你可以看到它一样(你只上课一个班级)。"limboo"

78赞 Nick 8/10/2016 #5

首先,我要说的是,我在这里主要谈论的是方法访问,在较小程度上,将类标记为最终访问,而不是成员访问。

古老的智慧

“除非你有充分的理由不这样做,否则将其标记为私有”

在开源主导开发者库领域之前,VCS/依赖管理在编写它的日子里是有道理的,这要归功于 Github、Maven 等。当时,通过限制图书馆的使用方式也可以赚钱。在我职业生涯的前 8 或 9 年里,我可能一直严格遵守这个“最佳实践”。

今天,我认为这是个坏建议。有时有一个合理的论据将方法标记为私有,或者将类标记为最终值,但这种情况非常罕见,即使如此,它也可能没有任何改进。

你有没有:

  • 对库等感到失望、惊讶或受伤,该库有一个错误,可以通过继承和几行代码来修复,但由于私有/最终方法和类被迫等待可能永远不会到来的官方补丁?我有。
  • 想将库用于与作者想象的略有不同的用例,但由于私有/最终方法和类而无法这样做?我有。
  • 对在可扩展性方面过于宽松的库等感到失望、惊讶或受伤?我没有。

以下是我听到的默认情况下将方法标记为私有的三个最大合理化:

合理化 #1:这是不安全的,没有理由覆盖特定方法

我数不清我错了多少次,不知道是否需要覆盖我编写的特定方法。在研究了几个流行的开源库之后,我艰难地了解到将事物标记为私有的真正成本。它通常消除了不可预见的问题或用例的唯一实用解决方案。相反,在 16+ 年的专业发展中,我从未后悔过出于与 API 安全相关的原因将方法标记为受保护而不是私有。当开发人员选择扩展一个类并重写一个方法时,他们有意识地说“我知道我在做什么”,为了提高生产力,这应该足够了。时期。如果它很危险,请在类/方法 Javadocs 中注意它,不要盲目地关上门。

默认受保护的标记方法可以缓解现代软件开发中的主要问题之一:想象力的失败。

合理化 #2:它保持公共 API / Javadocs 的清洁

这个更合理,根据目标受众,它甚至可能是正确的做法,但值得考虑的是保持 API“干净”的成本实际上是什么:可扩展性。由于上述原因,以防万一,将默认受保护的内容标记为可能更有意义。

合理化#3:我的软件是商业的,我需要限制它的使用。

这也是合理的,但作为消费者,我每次都会选择限制较少的竞争对手(假设不存在明显的质量差异)。

永不言败

我并不是说永远不要将方法标记为私有。我是说更好的经验法则是“除非有充分的理由不这样做,否则使方法受到保护”。

此建议最适合那些从事库或已分解为模块的大型项目的人。对于较小或更单一的项目,它往往并不重要,因为无论如何您都可以控制所有代码,并且在需要时可以轻松更改代码的访问级别。即便如此,我仍然会给出同样的建议:-)

评论

1赞 RBT 2/4/2017
W.r.t. ur - 当我将某些内容标记为受保护时,我选择明确这样做,因为我觉得这种行为不是最终的,可能是我的子类想要覆盖它的情况。如果我不太确定,那么我想默认将其设为私有。特别是在像 C# 这样的语言中,默认情况下将方法保持为非虚拟,仅添加受保护的访问说明符是没有意义的。您必须同时添加关键字才能使您的意图非常明确。此外,人们更喜欢关联而不是继承,因此很难察觉到默认Rationalization # 1virtualprotectedprotected
5赞 Nick 2/27/2017
恕我直言,使方法可重写所需的关键字组合是语言细节。我对合理化 #1 提出的反驳论点直接针对您提到的案例“如果我不太确定......”。实际上,如果您想不出为什么它会很危险,那么选择可扩展性可以获得更多收益。当你说“联想”时,我认为这是作曲的同义词,在这种情况下,我认为它并不重要。
4赞 shinkou 1/9/2018
我不记得有多少次我不得不“克隆”基础类,只是因为我想覆盖 1 或 2 或方法。就像你说的,随着我们共享的代码比以往任何时候都多的社会趋势,默认情况下使用受保护比私有更有意义。即对自己的逻辑更加开放。
1赞 Ron Dunn 5/27/2018
同意。我只是想调整 Java 的 BufferedReader 来处理自定义行分隔符。多亏了私有字段,我必须克隆整个类,而不是简单地扩展它并覆盖 readLine()。
29赞 Larry 11/4/2017 #6

停止滥用私有字段!!

这里的评论似乎压倒性地支持使用私有字段。好吧,那我有不同的话要说。

私人领域原则上好吗?是的。但是,当你不确定时,说黄金法则是让一切都保密,这绝对是错误的!在遇到问题之前,您不会看到问题。在我看来,如果您不确定,您应该将字段标记为受保护

在以下两种情况下,您希望扩展类:

  • 您希望向基类添加额外的功能
  • 您想要修改当前包之外的现有类(可能在某些库中)

在第一种情况下,私有字段没有错。人们滥用私有字段的事实使得当你发现你无法修改狗屎时,它是如此令人沮丧。

考虑一个对汽车进行建模的简单库:

class Car {
    private screw;
    public assembleCar() {
       screw.install();
    };
    private putScrewsTogether() {
       ...
    };
}

库作者认为:我库的用户没有理由需要访问实现细节吧?让我们将螺丝标记为私有。assembleCar()

好吧,作者错了。如果您只想修改方法而不将整个类复制到包中,那么您就不走运了。您必须重写自己的字段。假设这辆车使用了十几个螺丝,每个螺丝都涉及到一些不同私有方法的不平凡的初始化代码,这些螺丝都标有私有。在这一点上,它开始吸吮。assembleCar()screw

是的,你可以和我争论,库作者本可以写出更好的代码,所以私有字段没有错。我并不是说私有字段是 OOP 的问题。当人们使用它们时,这是一个问题。

这个故事的寓意是,如果你正在编写一个库,你永远不知道你的用户是否想访问一个特定的领域。如果您不确定,请标记它,以便以后每个人都会更快乐。至少不要滥用私人领域protected

我非常支持尼克的回答。

评论

3赞 Madara's Ghost 11/5/2017
使用私有字段主要是说继承是改变类/对象的某种行为的错误抽象(如果有意识地使用)。更改通常会以静默方式违反 LSP,因为行为会在不更改 API 的情况下更改。很好的答案,+1。
0赞 RecursiveExceptionException 8/21/2018
鼓吹!在尝试编写 Minecraft 模组时,Private 是我存在的祸根。没有什么比不得不使用 Reflection 或 ASM 来解决这个问题更烦人的了。
0赞 sean662 4/23/2020
据我所知,Nick 的回答主要是指方法,而不是属性。我完全同意我们不应该每次都声明方法私有,并且这些方法的使用需要深思熟虑,但是来吧,为什么您建议声明受保护的属性而不是私有属性,只是因为您想更轻松地“重写”类。完全分享你的挫败感,因为我每天都在与 3rd p 一起工作,但让我们鼓励人们创建可靠的架构,而不仅仅是说“代码最终会是垃圾,所以让我们从一开始就保护它,这样我们就可以很容易地重写它”。虽然我和你一样沮丧。