为什么“清洁代码”建议避免使用受保护的变量?


168

干净代码建议在“格式”一章的“垂直距离”部分中避免使用受保护的变量:

紧密相关的概念应在垂直方向上彼此靠近。显然,此规则不适用于属于单独文件的概念。但是,除非您有充分的理由,否则不应将紧密相关的概念分成不同的文件。确实,这是应避免使用受保护变量的原因之一

这是什么原因?


7
表明您认为一个例子,你需要
蚊蚋

63
我不会在那本书中把福音当作什么。
钻机2012年

40
@Rig,您在诸如此类的评论中都在亵渎这些部分。
Ryathal 2012年

40
我可以。我已经读过这本书,对此可以有自己的看法。
钻机2012年

10
我已经读过这本书,尽管我同意其中的大部分内容,但我也不会说我认为这是福音。我认为每种情况都是不同的..对于许多项目而言,要坚持书中所代表的确切准则是非常困难的。
西蒙·怀特海德

Answers:


277

应避免使用受保护的变量,因为:

  1. 它们往往导致YAGNI问题。除非您有一个后代类实际对受保护的成员进行填充,否则将其设为私有。
  2. 它们往往导致LSP问题。受保护的变量通常具有一些与之相关的固有不变性(否则它们将是公共的)。然后,继承者需要维护人们可能搞砸或故意侵犯的那些属性。
  3. 他们倾向于违反OCP。如果基类对受保护成员的假设过多,或者继承者对类的行为过于灵活,则可能导致该扩展修改基类的行为。
  4. 它们往往导致继承而不是组成。这往往导致更紧密的耦合,更多地违反SRP,更困难的测试以及“有利于继承而不是继承”讨论中的许多其他内容。

但是正如您所看到的,所有这些都是“倾向于”的。有时,受保护的成员是最优雅的解决方案。受保护的功能往往很少出现这些问题。但是有很多事情使他们受到谨慎对待。任何需要这种照顾的事情,人们都会犯错,而在编程世界中,这将意味着错误和设计问题。


感谢您有很多充分的理由。但是,这本书在格式和垂直距离的上下文中专门提到了它,这就是我想知道的部分。
Matsemann

2
鲍勃叔叔似乎是在某人指的是班级之间而不是同一班级中的受保护成员时,所指的是垂直距离。
罗伯特·哈维

5
@Matsemann-当然。如果我没记错的话,那部分将重点放在代码的可读性和发现性上。如果变量和函数在概念上起作用,则它们应在代码中紧密结合。受保护的成员将由派生类型使用,由于它们位于完全不同的文件中,因此派生类型不一定接近受保护的成员。
Telastyn 2012年

2
@ steve314我无法想象基类及其所有继承者只能生活在一个文件中的情况。如果没有示例,我倾向于认为这是对继承的滥用或对抽象的不良理解。
Telastyn 2012年

3
YAGNI =您肯定会需要它LSP =利斯科夫换人原则OCP =开/关原则SRP =单一责任原则
Bryan Glazer 2013年

54

与避免全局变量的原因相同,只是规模较小。这是因为很难在任何地方都找到正在读取(或更糟)的变量,并且很难使用法保持一致。如果使用受到限制(例如在派生类中的一个明显位置编写并在基类中的一个明显位置读取),则受保护的变量可使代码更清晰简洁。如果您想在多个文件中读写变量willy-nilly,最好将其封装。


26

我还没有读过这本书,但是我可以刺破鲍伯叔叔的意思。

如果穿上protected某些东西,则意味着一个类可以继承它。但是成员变量应该属于它们所包含的类。这是基本封装的一部分。放入protected成员变量会破坏封装,因为现在派生类可以访问基类的实现详细信息。当您public在普通类上创建变量时,也会发生相同的问题。

要解决此问题,可以将变量封装在一个受保护的属性中,如下所示:

protected string Name
{
    get { return name; }
    private set { name = value; }
}

这允许name使用构造函数自变量从派生类中进行安全设置,而无需暴露基类的实现细节。


您使用受保护的二传手创建了一个公共财产。应该使用带有私有设置程序的受保护属性来解决保护字段。
安迪

2
现在+1。我想也可以保护Setter,但是无论新值是否有效,base都可以强制执行的点ID。
安迪

只是提供相反的观点-我将所有变量都保护在基类中。如果仅通过公共接口访问它们,则不应将其作为派生类,而应仅包含基类对象。但我也避免层次超过2班深
马丁贝克特

2
和成员之间存在巨大差异。如果公开一个公共成员,则意味着所有派生类型必须具有相同的成员相同的工作方式,因为实例可能会传递给接收引用并希望使用该成员的代码。相反,如果公开了一个成员,则可能希望访问该成员,但不能是任何其他比。protectedpublicBaseTypeDerivedTypeBaseTypeBaseTypeprotectedDerivedTypebasebaseBaseType
2012年

18

Bob叔叔的论点主要是距离问题之一:如果您有一个对班级很重要的概念,请将该概念与该班级捆绑在该文件中。没有分隔在磁盘上的两个文件中。

受保护的成员变量分散在两个地方,有点像魔术。你引用此变量,但它没有在这里定义...这里如何界定?于是狩猎开始了。protected他的论点最好完全避免。

现在,我不认为该规则始终需要遵守该字母(例如:您不得使用protected)。看看他所获得的精神 ...将相关内容捆绑到一个文件中-使用编程技术和功能来做到这一点。我建议您不要过度分析,以免陷入细节。


9

这里已经有一些非常好的答案。但要添加的一件事:

在大多数现代的OO语言中,将每个类放入自己的文件中是一个好习惯(在Java AFAIK中是必要的)(请不要对C ++有所了解,因为C ++中经常有两个文件)。

因此,实际上不可能将函数保留在派生类中,并使源代码中的基类的受保护变量在源代码中“垂直接近”变量定义。但是理想情况下应将它们保留在此,因为受保护的变量是供内部使用的,这使得使用它们的函数通常是“紧密相关的概念”。


1
不在Swift中。有趣的是,Swift没有“保护”,但有“ fileprivate”,它同时取代了“保护”和C ++“朋友”。
gnasher729

@ gnasher729-当然,Swift在其传统中有很多Smalltalk。Smalltalk除了私有变量和公共方法外没有其他任何东西。Smalltalk中的private表示private:即使是同一类的两个对象也无法访问彼此的变量。
Jules

4

基本思想是,声明为“保护”的“字段”(实例级变量)可能比它必须的可见,而“保护”的程度比您想要的少。C / C ++ / Java / C#中没有访问修饰符,该修饰符等同于“只能由同一程序集中的子类访问”,因此使您能够定义自己的子程序,这些子程序可以访问程序集中的字段,但是不允许在其他程序集中创建的子级具有相同的访问权限;C#具有内部修饰符和受保护的修饰符,但是将它们组合在一起可使访问成为“内部修饰符”,而不是“内部修饰符”。因此,无论您写的是该孩子还是其他人,任何孩子都可以访问受保护的字段。因此,受保护成为黑客的门户。

同样,根据字段的定义,更改字段时几乎没有固有的验证。在C#中,您可以使一个只读,从而使值类型有效地保持不变,并且引用类型无法重新初始化(但仍然非常可变),仅此而已。这样,即使受到保护,您的孩子(您不能信任)也可以访问此字段并将其设置为无效字段,从而使对象的状态不一致(应避免使用)。

公认的使用字段的方法是将它们设置为私有并使用属性和/或getter和setter方法访问它们。如果该类的所有消费者都需要该值,则(至少)将获取者公开。如果只有孩子需要,请保护吸气剂。

回答问题的另一种方法是问自己。为什么子方法中的代码需要直接修改我的状态数据的能力?那关于那个代码怎么说?那是表面上的“垂直距离”参数。如果子代中有必须直接更改父代状态的代码,那么也许该代码应该首先属于父代?


4

这是一个有趣的讨论。

坦率地说,好的工具可以缓解许多这些“垂直”问题。实际上,在我看来,“文件”的概念实际上阻碍了软件开发-Light Table项目正在努力解决这一问题(http://www.chris-granger.com/2012/04/12/light-表格---新概念/)。

将文件从图片中删除,受保护的范围变得更具吸引力。受保护的概念在SmallTalk之类的语言中变得最为清晰-任何继承都只是扩展了类的原始概念。它应该做所有并拥有父类所做的一切。

我认为,类层次结构应尽可能浅,大多数扩展来自组合。在这样的模型中,我看不出有任何理由导致私有变量被保护。如果扩展类应该表示包括父类的所有行为的扩展(我认为应该如此,因此认为私有方法本质上是不好的),那么扩展类为什么也不能表示此类数据的存储呢?

换句话说,在任何我认为私有变量比受保护变量更适合的情况下,继承都不是解决问题的方法。


0

如此处所说,这只是原因之一,即逻辑链接的代码应放在物理链接的实体(文件,程序包和其他)中。在较小的规模上,这与您没有放置一个类的原因相同,该类使具有显示UI的类的数据库查询进入程序包。

但是,不建议使用受保护的变量的主要原因是因为它们倾向于破坏封装。变量和方法应具有尽可能有限的可见性;有关更多参考,请参见Joshua Bloch的有效Java条款 13-“最小化类和成员的可访问性”。

但是,您不应将所有这些广告都当作公会来使用;如果受保护的变量非常糟糕,则它们根本不会放在语言中。imho保护字段的合理位置是在测试框架的基类内部(集成测试类对其进行了扩展)。


1
不要以为因为某种语言允许这样做就可以了。我不确定允许保护区的确有上帝的道理;我们实际上不允许他们在我的雇主那里。
安迪

2
@安迪你还没看我说的话;我说过不建议使用受保护的字段,并说明其原因。显然,在某些情况下需要保护变量。您可能只希望在子类中有一个恒定的最终值。
m3th0dman 2012年

然后,您可能希望重新编写部分帖子,因为您似乎可以互换地使用变量和字段,并明确表示将保护consts这样的事情视为例外。
安迪

3
@Andy很明显,由于我指的是受保护的变量,所以我不是在谈论局部变量或参数变量,而是在谈论字段。我还提到了非恒定保护变量有用的情况。恕我直言,公司强制执行程序员编写代码的方式是错误的。
m3th0dman'8

1
如果很明显,我不会感到困惑和低落。该规则由我们的高级开发人员制定,我们决定运行StyleCop,FxCop并要求进行单元测试和代码审查。
安迪

0

我发现对JUnit测试使用受保护的变量会很有用。如果将它们设为私有,则在测试过程中如果没有反思就无法访问它们。如果要通过许多状态更改来测试复杂的过程,这将很有用。

您可以使用受保护的getter方法将它们设置为私有,但是如果变量仅在JUnit测试期间将在外部使用,我不确定这是一个更好的解决方案。


-1

是的,我同意这有点奇怪,因为(我记得)该书还将所有非私有变量都作为需要避免的话题​​进行了讨论。

我认为protected修饰符的问题在于,不仅成员暴露于子类,而且整个包也可以看到该成员。我认为鲍勃叔叔是这双重方面的例外。当然,不能总是避开使用受保护的方法以及受保护的变量限定条件。

我认为一种更有趣的方法是将所有内容公开,这将迫使您使用小类,从而使整个垂直距离度量有些争议。

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.