我很少使用继承,但当我使用继承时,我从不使用受保护的属性,因为我认为它破坏了继承类的封装。

您使用受保护的属性吗?你用它们做什么?

有帮助吗?

解决方案

在这个 面试 比尔·维纳斯 (Bill Venners) 和约书亚·布洛赫 (Joshua Bloch) 的《设计》 有效的Java 说:

信任子类

比尔·维纳斯: 我是否应该比非群级更亲密地信任子类?例如,我是否使子类实现更容易打破我,而不是一个非群体?特别是,您对受保护的数据有何看法?

乔什·布洛赫: 假设您可以通过子类访问您的内部数据结构的访问,那么要编写对恶意子类的既可以进行且可靠的东西实际上是一件很难的事情。如果子类无法访问普通用户所无法的任何内容,那么子类造成损坏就很难。但是,除非您将所有方法最终确定,否则该子类仍可以通过响应方法调用来完成错误的事情来破坏您的合同。这就是为什么诸如字符串之类的安全关键类是最终的原因。否则,有人可以写一个使字符串显得可变的子类,这足以打破安全性。因此,您必须相信自己的子类。如果您不信任他们,那么您将无法允许他们,因为子类很容易导致班级违反其合同。

至于受保护的数据,这是必要的邪恶。应将其保持在最低限度。大多数受保护的数据和受保护的方法等于致力于实施细节。受保护的字段是您对子类可见的实现细节。即使是受保护的方法也是您使子类可见的一件内部结构。

您之所以能看到的原因是,要允许子类能够完成工作或有效地完成工作,通常是必要的。但是,一旦您做到了,就可以致力于它。现在,即使您以后发现更有效的实现不再涉及使用特定字段或方法的更有效的实现,现在也无法更改。

因此,所有其他事情都是平等的,您根本不应该有任何受保护的成员。但是,这就是说,如果您太少,那么您的班级可能无法用作超级阶级,或者至少不能作为高效的超级班级。通常,您发现事实之后。我的理念是,当您第一次写班级时,受保护成员尽可能少。然后尝试对其进行子类化。您可能会发现,如果没有特定的保护方法,所有子类都必须做一些坏事。

举个例子,如果你看一下 AbstractList, ,您会发现有一种受保护的方法一次删除列表的范围(removeRange)。为什么会在那里?因为基于公共API删除范围的正常习语是为了致电 subList 得到一个子List,然后致电 clear 在那个子上List. 。但是,没有这种特定的受保护方法,唯一的事情就是 clear 可以做的是反复删除单个元素。

想一想。如果您有数组表示,它将做什么?它会反复倒塌数组,进行n次工作。因此,这将需要大量的工作,而不是应有的线性工作。通过提供此受保护的方法,我们允许任何可以有效删除整个范围的实现。以及任何合理的 List 实施可以一次更有效地删除范围。

我们需要这种受保护的方法是您必须比我更聪明才能知道的。基本上,我实施了这件事。然后,当我们开始对其进行亚类时,我们意识到范围删除是二次的。我们负担不起,所以我采用了受保护的方法。我认为这是受保护方法的最佳方法。放入尽可能少的地方,然后根据需要添加更多。受保护的方法代表了您可能想要更改设计的承诺。您总是可以添加受保护的方法,但不能将其取出。

比尔·维纳斯: 和受保护的数据?

乔什·布洛赫: 同样的事情,但甚至更多。在弄乱数据不变的方面,受保护的数据更加危险。如果您让其他人访问某些内部数据,他们将免费统治它。

简洁版本:它破坏了封装,但它是一种必要的罪恶,应该保持在最低限度。

其他提示

C#:

我将 protected 用于我希望基类重写的抽象或虚拟方法。如果一个方法可以被基类调用,我也将其设置为受保护,但我不希望在类层次结构之外调用它。

您可能需要它们作为静态(或“全局”)属性,您希望同一包中的子类或类(如果是关于 java)从中受益。

那些代表某种“常量值”的静态最终属性很少有 getter 函数,因此受保护的静态最终属性在这种情况下可能有意义。

斯科特·迈耶斯 说 在Effective C++(第三版)中使用受保护的属性:

第 22 项:将数据成员声明为私有。

原因和你给出的一样:它破坏了封装。结果是,否则对类布局的局部更改可能会破坏依赖类型并导致许多其他地方的更改。

从来没有任何充分的理由来保护属性。基类必须能够依赖于状态,这意味着通过访问器方法限制对数据的访问。您不能让任何人访问您的私人数据,甚至是儿童。

protected 关键字是一个概念错误和语言设计失误,以及几种现代语言,例如 Nim 和 Ceylon(请参阅 http://ceylon-lang.org/documentation/faq/language-design/#no_protected_modifier),经过精心设计而不是仅仅复制常见错误,没有这样的关键字。

破坏封装的不是受保护的成员,而是暴露不应该暴露的成员破坏了封装......它们是受保护的还是公开的并不重要。问题在于 protected 是它是错误的和误导性的......宣布成员 protected (而不是 private) 并不能保护它们,它的作用相反,完全一样 public 做。受保护的成员可以在类外部访问,暴露于世界,因此它的语义必须永远维护,就像 public. 。“受保护”的整个想法都是无稽之谈......封装并不安全,关键字只会进一步混淆两者。您可以通过避免所有使用来提供一点帮助 protected 在你自己的类中——如果某些东西是实现的内部部分,不是类语义的一部分,并且将来可能会发生变化,那么将其设为私有或内部到你的包、模块、程序集等中。如果它是类语义中不可更改的部分,那么将其公开,这样您就不会惹恼类的用户,他们可以看到文档中有一个有用的成员,但无法使用它,除非他们正在创建自己的成员。拥有自己的实例,并且可以通过子类化来获取它。

我不在 Java 中使用受保护的属性,因为它们只是受包保护。但在C++中,我将在抽象类中使用它们,允许继承类直接继承它们。

一般来说,不,您真的不想使用受保护的数据成员。如果您编写 API,则更是如此。一旦有人继承了你的类,你就永远无法真正进行维护,并且不会以某种奇怪的、有时是疯狂的方式破坏它们。

我认为受保护的属性是一个坏主意。我用 格子样式 与我的 Java 开发团队强制执行该规则。

我最近从事的一个项目是“受保护”成员是一个非常好的主意。类层次结构是这样的:

[+] Base
 |
 +--[+] BaseMap
 |   |
 |   +--[+] Map
 |   |
 |   +--[+] HashMap
 |
 +--[+] // something else ?

Base 实现了一个 std::list 但没有其他任何东西。用户禁止直接访问列表,但由于基类不完整,它无论如何都要依赖派生类来实现对列表的间接访问。

间接可以来自至少两种形式: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。我不可能将这些“私有”,因为无论如何我都会通过读/写访问器公开它们的所有或几乎所有功能。

最后,我想如果您最终将类划分为多个对象,每个派生都向其母类添加所需的功能,并且只有最派生的类真正可用,那么 protected 就是正确的选择。事实上“受保护的成员”是一个类,因此几乎不可能“破坏”,这一事实有所帮助。

但除此之外,应尽可能避免 protected (即:默认情况下使用 private,当必须公开该方法时使用 public)。

我用它们。简而言之,如果您想共享一些属性,这是一个好方法。当然,您可以为它们编写 set/get 函数,但如果没有验证,那还有什么意义呢?它也更快。

考虑一下:你有一个类是你的基类。它有很多您不想在子对象中使用的属性。您可以为每个函数编写一个获取/设置函数,也可以只设置它们。

我的典型示例是文件/流处理程序。您想要访问处理程序(即文件描述符),但您想对其他类隐藏它。这比为其编写 set/get 函数要容易得多。

一般来说,是的。受保护的方法通常更好。

在使用中,通过使用受保护的 最终的 由类的所有子级共享的对象的变量。我始终建议不要将其与基元或集合一起使用,因为无法为这些类型定义合同。

最近,我开始将使用原语和原始集合所做的事情与使用格式良好的类所做的事情分开。基元和集合应该始终是私有的。

另外,当公共成员变量被声明为final并且是不太灵活的格式良好的类(同样,不是基元或集合)时,我开始偶尔公开它们。

这不是什么愚蠢的捷径,我认真地思考了一下,并认为公众之间绝对没有区别 最终的 暴露一个对象和一个 getter 的变量。

这取决于你想要什么。如果您想要一个快速的类,那么应该保护数据并使用受保护和公共方法。因为我认为你应该假设从你的类派生的用户非常了解你的类,或者至少他们已经阅读了他们要覆盖的函数的手册。

如果你的用户搞乱了你的类,那不是你的问题。每个恶意用户都可以在覆盖您的虚拟机之一时添加以下行:

(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 方法访问其成员变量。这太过分了,因为如果一个类不能信任自己的私有方法和自己的私有数据,那么它还能信任谁呢?

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top