我很少使用继承,但是当我这样做时,我从不使用受保护的属性,因为我认为它打破了继承类的封装。
你使用受保护的属性吗? 你用它们做什么的?
在Bill Venners的设计访谈中 , Effective Java的作者Joshua Bloch说:
信任子类
Bill Venners: 我是否应该比非子类更亲密地信任子类? 例如,我是否使子类实现更容易打破我,而不是非子类? 特别是,您对受保护数据的看法如何?
Josh Bloch:假设你给子类访问你的内部数据结构,那么编写一个既可子类化又强健对抗恶意子类的东西实际上是一件非常困难的事情。 如果子类无法访问普通用户没有访问的任何内容,则子类更难以进行破坏。 但除非你将所有方法都设置为final,否则子类仍然可以通过执行错误的操作来响应方法调用来破坏你的契约。 这就是为什么像String这样的安全关键类是最终的。 否则,有人可能会编写一个子类,使Strings看起来可变,这足以破坏安全性。 所以你必须信任你的子类。 如果您不信任它们,那么您就不能允许它们,因为子类很容易导致类违反其合同。
就一般受保护的数据而言,这是一种必要的恶魔。 它应该保持在最低限度。 大多数受保护的数据和受保护的方法相当于提交实现细节。 受保护的字段是您对子类可见的实现细节。 即使受保护的方法也是一个内部结构,您可以使子类可见。
使其可见的原因在于,为了允许子类完成工作或有效地执行工作,通常需要这样做。 但是一旦你完成它,你就会致力于它。 即使您后来发现不再涉及使用特定字段或方法的更有效的实现,现在也是您不允许更改的内容。
所以在所有其他条件相同的情况下,你根本不应该有任何受保护的成员。 但是,如果你的人数太少,那么你的班级可能无法用作超级班级,或者至少不是一个有效的超级班级。 通常你会发现事后。 我的理念是,当你第一次上课时,尽可能少的受保护成员。 然后尝试将其子类化。 您可能会发现,如果没有特定的受保护方法,所有子类都必须做一些坏事。
例如,如果你看一下
AbstractList
,你会发现有一个受保护的方法可以一次删除列表范围(removeRange
)。 为什么那里? 因为基于公共API删除范围的正常习惯是调用subList
来获取子List
,然后在该子List
上调用clear
。 如果没有这种特殊的保护方法,但是,唯一clear
可以做的是反复删除单个元素。想一想。 如果你有一个数组表示,它会做什么? 它将反复折叠数组,执行N次N次。 因此,它需要一定数量的工作,而不是它应该的线性工作量。 通过提供这种受保护的方法,我们允许任何可以有效删除整个范围的实现。 任何合理的
List
实现都可以一次性更有效地删除范围。我们需要这种受保护的方法,你需要比我更聪明才能预先知道。 基本上,我实现了这个东西。 然后,当我们开始对它进行子类化时,我们意识到范围删除是二次的。 我们买不起,所以我加入了受保护的方法。 我认为这是受保护方法的最佳方法。 尽可能少地放入,然后根据需要添加更多。 受保护的方法代表您可能想要更改的设计承诺。 您始终可以添加受保护的方法,但无法将其取出。
Bill Venners: 受保护的数据?
乔什布洛赫:同样的事情,但更多。 在弄乱数据不变量方面,受保护的数据更加危险。 如果您让其他人访问某些内部数据,他们可以免费统治它。
简短版本:它打破了封装,但它是一个必须保持最低限度的必要的邪恶。
C#:
我将protected用于抽象或虚拟方法,我希望基类重写。 如果它可以被基类调用,我也会使方法受到保护,但我不希望它在类层次结构之外调用。
您可能需要它们用于静态(或“全局”)属性,您希望您的子类或来自相同包的类(如果它是关于Java)受益。
表示某种“常量值”的静态最终属性很少有getter函数,因此受保护的静态final属性在这种情况下可能有意义。
Scott Meyers说不要在Effective C ++中使用受保护的属性(第3版):
第22项:声明数据成员私有。
原因与您给出的相同:它打破了封装。 结果是,否则对类布局的局部更改可能会破坏依赖类型并导致许多其他位置的更改。
protected
关键字是概念性错误和语言设计的拙劣,以及一些现代语言,例如Nim和Ceylon(请参阅http://ceylon-lang.org/documentation/faq/language-design/#no_protected_modifier ),设计而不是仅仅复制常见错误,没有这样的关键字。
它不是破坏封装的受保护成员,它暴露了不应暴露的成员破坏了封装...它们是受保护还是公共无关紧要。 protected
的问题在于它是错误的和误导性的......声明protected
成员(而不是private
)不能保护他们,它正好与public
一样。 受保护的成员,可以在课堂外访问,暴露于世界,因此必须永久保持其语义,就像public
的情况一样。 “受保护”的整个想法是无稽之谈......封装不是安全性,关键字只会加剧两者之间的混淆。 您可以通过避免在您自己的类中使用protected
所有内容来帮助一点 - 如果某些内容是实现的内部部分,不是类的语义的一部分,并且可能在将来发生变化,然后将其设置为私有或内部你的包,模块,程序集等等。如果它是类语义的一个不可改变的部分,那么把它公之于众,那么你就不会惹恼你的班级用户,他们可以看到文档中有一个有用的成员但是可以'使用它,除非他们正在创建自己的实例并且可以通过子类化来实现它。
我不在Java中使用受保护的属性,因为它们只受到包保护。 但是在C ++中,我将在抽象类中使用它们,允许继承类直接继承它们。
拥有受保护的属性从来没有任何充分的理由。 基类必须能够依赖于状态,这意味着通过访问器方法限制对数据的访问。 你不能让任何人访问你的私人数据,甚至是儿童。
我最近在一个项目上工作过的“受保护”成员是一个非常好的主意。 类层次结构是这样的:
[+] Base
|
+--[+] BaseMap
| |
| +--[+] Map
| |
| +--[+] HashMap
|
+--[+] // something else ?
Base实现了一个std :: list但没有别的。 用户禁止直接访问列表,但由于Base类不完整,因此无论如何它依赖于派生类来实现对列表的间接访问。
间接可以来自至少两种风格:std :: map和stdext :: hash_map。 两个映射的行为方式都相同,但事实上hash_map需要Key可以清除(在VC2003中,可转换为size_t)。
因此BaseMap将TMap实现为模板化类型,它是类似地图的容器。
Map和HashMap是BaseMap的两个派生类,一个在std :: map上专门定位BaseMap,另一个在stdext :: hash_map上。
所以:
Base不能这样使用(没有公共访问器!),只提供了通用功能和代码
BaseMap需要轻松读/写std :: list
Map和HashMap需要对BaseMap中定义的TMap进行简单的读/写访问。
对我来说,唯一的解决方案是对std :: list和TMap成员变量使用protected。 我无法将这些“私有”放在那里,因为无论如何我都会通过读/写访问器公开所有或几乎所有的功能。
最后,我想如果你把你的类分成多个对象,每个派生都需要为它的母类添加所需的特性,并且只有最派生的类才真正可用,那么保护就是要走的路。 事实上,“受保护的成员”是一个阶级,因此几乎不可能“打破”,帮助。
但除此之外,应尽可能避免受保护(即:默认情况下使用private,必须公开方法时使用public)。
这取决于你想要什么。 如果你想要一个快速类,那么应该保护数据并使用protected和public方法。 因为我认为您应该假设从您的班级派生的用户非常了解您的课程,或者至少他们已经在他们要覆盖的功能上阅读了您的手册。
如果您的用户弄乱了您的课程,那不是您的问题。 在覆盖其中一个虚拟机时,每个恶意用户都可以添加以下行:
(C#)
static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...
你不能密封每一堂课以防止这种情况发生。
当然,如果您想要对某些字段进行约束,那么请使用存取函数或属性或您想要的东西,并将该字段设为私有,因为没有其他解决方案......
但我个人不喜欢不惜一切代价坚持oop原则。 特别是制作属性的唯一目的是使数据成员私有化。
(C#):
private _foo;
public foo
{
get {return _foo;}
set {_foo=value;}
}
这是我个人的意见。
但要做你的老板要求(如果他想要私人领域而不是那样做。)
我在基类中使用受保护的变量/属性,我知道我不打算更改为方法。 这样,子类可以完全访问其继承的变量,并且没有(人为创建的)通过getter / setter访问它们的开销。 一个例子是使用底层I / O流的类; 没有理由不允许子类直接访问底层流。
这适用于在基类和所有子类中以直接简单方式使用的成员变量。 但是对于使用更复杂的变量(例如,访问它会导致类中其他成员的副作用),直接可访问的变量是不合适的。 在这种情况下,可以将其设为私有,并且可以提供公共/受保护的getter / setter。 一个示例是基类提供的内部缓冲机制,其中直接从子类访问缓冲区会损害基类用于管理它们的算法的完整性。
这是一个设计判断决策,基于成员变量的简单程度,以及未来版本中预期的结果。
封装是很好的,但它可以采取太多。 我见过自己的私有方法只使用getter / setter方法访问其成员变量的类。 这是过度的,因为如果一个类不能信任自己的私有方法和它自己的私有数据,它可以信任谁?
通常,您真的不想使用受保护的数据成员。 如果您编写API,这是双重的。 一旦有人从你的班级继承,你就永远不会真正做到维护,也不会以某种奇怪的,有时是狂野的方式打破它们。
我用它们。 简而言之,如果您想要共享一些属性,这是一个好方法。 当然,您可以为它们编写set / get函数,但如果没有验证,那么重点是什么? 它也更快。
考虑一下:你有一个类是你的基类。 它有很多属性你不想在子对象中使用。 你可以为每个编写一个get / set函数,或者你可以设置它们。
我的典型示例是文件/流处理程序。 您想要访问处理程序(即文件描述符),但是您想要将其从其他类中隐藏。 这比为它编写set / get函数更容易。
我认为受保护的属性是个坏主意。 我使用CheckStyle与我的Java开发团队一起强制执行该规则。
一般来说,是的。 受保护的方法通常更好。
在使用中,通过对一个类的所有子级共享的对象使用受保护的最终变量,可以实现一定程度的简单性。 我总是建议不要将它与原语或集合一起使用,因为合同不可能为这些类型定义。
最近,我将你用原型和原始集合与你用格式良好的类做的东西分开。 原始和收藏品应始终是私人的。
此外,我偶尔会在公共成员变量声明为final时公开它们,并且是格式良好的类,它们不是太灵活(同样,不是原语或集合)。
这不是一些愚蠢的捷径,我认为它非常认真,并且决定暴露一个对象和一个getter的公共最终变量之间绝对没有区别。