私人与受保护-可见性良好做法关注


145

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

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

很好,问题是,它们之间的实际区别是什么?什么private时候使用protected?什么时候使用?在这方面是否有标准或可接受的良好实践?

到目前为止,为了保留继承和多态的概念,我使用public应从外部访问的所有内容(例如构造函数和主类功能),以及protected内部方法(逻辑,辅助方法等)。我在正确的轨道上吗?

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


33
有关系吗?任何具有OOP支持的语言都存在此问题。我碰巧用PHP编程,但是我认为这个问题适用于任何OOP支持语言。
马达拉的鬼魂

2
好吧,很公平,只是想知道您是否忘记标记了。现在,我看到oop标签。
Paul Bellora

我必须为该类的每个属性设置可见性(私有/公共/受保护)?还是只有其中一些需要具有可见性类型?如果是,如何确定哪个属性需要在类顶部设置可见性?
user4271704 '16

1
从另一方面来说,受保护的和私有的区别似乎很明显。如果子类将使用方法/变量,则使用protected;否则,使用private。具体来说,如果子类必须在父级中重新定义非常相似的私有变量,则只需对其进行保护即可。
iPherian '16

internalC#语言中还有另一个访问说明符,它限制了物理程序集中的类或其成员的访问级别。不过,我不确定它是否支持或其他语言是否类似。
RBT

Answers:


128

不,您的路线不正确。一个好的经验法则是:使所有内容尽可能私密。这使您的类更具封装性,并允许更改类的内部结构而不会影响使用您的类的代码。

如果您将类设计为可继承的,那么请仔细选择可以从子类重写和访问的类,并对其进行保护(最后说一句Java,如果要使其可访问但不可重写)。但是请注意,一旦您接受了该类的子类,并且有一个受保护的字段或方法,则此字段或方法就是该类的公共API的一部分,并且在以后不破坏子类的情况下可能不会更改。

不应继承的类应设为final(在Java中)。为了进行单元测试,您可以放宽一些访问规则(从私有到受保护,从最终到非最终),然后记录下来,并明确说明尽管该方法是受保护的,但不应覆盖该方法。


1
真正的问题是关于privatevs。protected何时要继承属性,何时不继承?我真的不能说出将来某个时候用户是否想参加我的课程并扩展它……
Madara的Ghost

16
好吧,问题不是用户覆盖的内容,而是您要允许覆盖的内容。通常,这有助于改变立场并尝试思考:如果我使用该类并创建了一个子类,那么我希望能够覆盖什么?有时其他用户仍然会错过一些东西……
Thorsten Dittmar '12

3
在继承中,受保护的字段是不好的做法!。请当心。就是为什么
RBT

4
在继承中,私有字段实际上是不好的!(夸张)请阅读我的回答。
拉里

6
典型的控制狂观点。
bruno desthuilliers

58

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

老智慧

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

在开放源代码主导开发人员库空间和VCS /依赖关系mgmt之前的几天里,它就很有意义。在Github,Maven等的帮助下成为超级协作。那时,通过限制图书馆的利用方式也可以赚钱。我可能在职业生涯的前8到9年中严格遵循了这种“最佳实践”。

今天,我认为这是个坏建议。有时有一个合理的论据将一个方法标记为私有,或将一个类标记为final,但这种情况极为罕见,即使那样也可能没有任何改善。

你有没有:

  • 被库等感到失望,惊讶或伤害,该库具有可以通过继承和少量代码修复的错误,但是由于私有/最终方法和类被迫等待可能永远不会出现的正式补丁?我有。
  • 是否希望将库用于与作者想象的稍有不同的用例,但由于私有/最终方法和类而无法使用?我有。
  • 是否对库的可扩展性过于宽容感到失望,惊讶或伤害?我还没有。

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

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

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

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

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

这是更合理的做法,并且取决于目标受众,这甚至可能是正确的选择,但是值得考虑的是保持API“干净”的代价实际上是:可扩展性。出于上述原因,为防万一,标记默认情况下受保护的内容可能更有意义。

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

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

永远不要把话说绝了

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

该建议最适合那些正在分解成模块的图书馆或大型项目的人员。对于更小或更单一的项目,这无关紧要,因为您始终控制所有代码,并且在需要时很容易更改代码的访问级别。即使那样,我仍然会给出相同的建议:-)


Wrt your- Rationalization # 1当我将某项标记为受保护时,我选择明确地执行此操作,因为我认为这种行为不是最终的,并且可能是我的子类希望覆盖它的情况。如果不确定,则默认情况下将其设为私有。特别是在像C#这样的语言中,默认情况下该方法保持非虚拟方法,仅添加受保护的访问说明符是没有意义的。您必须同时添加virtualprotected关键字,才能使意图非常清楚。此外,人们更喜欢关联而不是继承,因此protected默认值很难理解
RBT

4
使方法可重写所必需的关键字组合是语言详细信息恕我直言。我对合理化1提出的反对论点直指您提到“如果我不太确定...”的情况。实际上,如果您无法想到为什么这样做会很危险,那么通过选择可扩展性将获得更多的收益。当您说“关联”时,我将其视为合成的代名词,在这种情况下,我认为这都不重要。
尼克

3
我不记得仅仅因为我想重写1或2或方法而不得不“克隆”基础类多少次。就像您所说的那样,随着社会趋势,我们共享比以往任何时候都更多的代码,默认情况下,使用受保护比私有更有意义。即对自己的逻辑更开放。
shinkou

1
同意。我只是想调整Java的BufferedReader以处理自定义行定界符。多亏了私有字段,我不得不克隆整个类,而不是简单地扩展它并覆盖readLine()。
罗恩·邓恩'18

21

停止滥用私人领域!!!

这里的评论似乎绝大多数支持使用私有字段。好吧,那么我有话要说。

私人领域原则上好吗?是。但是说一个黄金法则是在不确定时将所有内容都保密,这肯定 是错误的!在碰到一个问题之前,您不会看到问题。我认为,如果不确定则应将字段标记为受保护

您要扩展类有两种情况:

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

在第一种情况下,私有字段没有错。人们滥用私人领域的事实使您在发现自己无法修改时就感到沮丧。

考虑一个简单的汽车模型库:

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

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

好吧,作者是错的。如果您只想修改assembleCar()方法而不将整个类复制到程序包中,那么您很不走运。您必须重写自己的screw字段。假设这辆车使用了十二个螺钉,并且每个螺钉都以不同的私有方法包含一些非常规的初始化代码,并且这些螺钉都标记为私有。在这一点上,它开始吮吸。

是的,您可以和我争论说,库作者可以编写更好的代码,所以私有字段没有错。我并不是说私有领域是OOP的问题。人们在使用它们时会出现问题。

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

我非常支持尼克的回答。


2
使用私有字段通常是说继承是错误的抽象,它会改变类/对象的某种行为(如果有意识地使用)。在不更改API的情况下更改行为时,更改通常会默默地违反LSP。好的答案,+ 1。
马达拉的鬼魂

传教!尝试编写Minecraft Mod时,私人存在是我生存的祸根。没有比使用反射或ASM修复它更令人讨厌的了。
RecursiveExceptionException

据我了解尼克的答案,他主要是指方法而不是属性。我完全同意,我们不应该每次都将方法声明为私有,并且使用这些方法时需要考虑周全,但是请问为什么您会建议仅仅因为您想要更轻松地“重写”该类而声明对私有属性进行保护的属性。在处理3rd p的日常工作时,请完全与您分享您的沮丧之情,但让我们鼓励人们创建可靠的体系结构,而不仅仅是说“代码最终会被废弃,因此我们从一开始就受到保护,因此我们可以轻松地重写它”。虽然我也分享您的无奈。
sean662

16

我前一段时间读过一篇文章,该文章讨论了尽可能多地锁定每个类。除非您迫切需要将某些数据或功能暴露给外界,否则请使所有内容均为最终私有。将范围扩展到以后更容易被接受总是很容易的,但是反之则不行。首先考虑让尽可能多的东西尽可能final这将使之间进行选择privateprotected容易得多。

  1. 除非您需要立即对它们进行子类化,否则将所有类定为最终类。
  2. 使所有方法最终化,除非您需要立即子类化并覆盖它们。
  3. 将所有方法参数定为最终值,除非您需要在方法主体中进行更改,否则无论如何,这在大多数情况下都是很尴尬的。

现在,如果您要上最后一堂课,那么将所有内容都设为私有,除非世界上绝对需要某些东西-将其公开。

如果剩下一个带有子类的类,请仔细检查每个属性和方法。首先考虑是否要将该属性/方法公开给子类。如果这样做,则考虑子类是否会在覆盖过程中使属性值或方法实现混乱,从而对对象造成严重破坏。如果有可能,并且您甚至想从子类中保护类的属性/方法(听起来很讽刺,我知道),则将其设为私有。否则使其受到保护。

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


2
需要说明的是:final classJava中的A 是不能从其继承的类。A final method是非虚拟的,即不能被子类覆盖(Java方法默认为虚拟)。A final field/parameter/variable是不可变的变量引用(从某种意义上说,在初始化之后就不能对其进行修改)。
M.Stramm 2015年

1
有趣的是,我已经看到了完全相反的建议,除非确定覆盖该问题有可能破坏不变式,否则没有什么是最终的。这样,任何人都可以根据需要自由扩展您的课程。
GordonM

您能否在您的编程生涯中列举一个案例,其中将方法参数标记为“最终”对您的理解有所不同?(除非编译器要求,否则)
user949300 '17

7

什么private时候使用protected?什么时候使用?

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

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


2
“根据关系实施”是HAS-A关系。如果所有属性都是私有的,则访问它们的唯一方法是通过方法。这就像子类包含超类的对象一样。从具体类中消除所有受保护的属性也意味着也要消除它们的所有继承。
shawnhcorey 2015年

2
有趣的思想过程- 私人继承。@shawnhcorey感谢您明确指出它指向的是HAS-A关系,即关联(可以是聚合或组合)。我认为该帖子也应该进行更新以阐明相同内容。
RBT

1

好吧,如果paybill类处理付款记帐,那么封装就是全部,那么在产品类中,为什么它需要整个记帐过程,即付款方法如何在哪里付款,所以只让其他类和对象使用什么对于那些也会使用其他类的人来说,只不过是公共的而已,仅针对扩展类的那些限制而受到保护。由于您是madara uchiha的私人,就像"limboo"您可以看到它一样(您只上一堂课)。

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.