15

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

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

4

14 回答 14

20

在Bill Venners 对 Design 的采访中, Effective Java的作者 Joshua Bloch说:

信任子类

Bill Venners: 我应该比非子类更信任子类吗?例如,我是否让子类实现比非子类更容易破坏我?特别是,您如何看待受保护的数据?

乔什·布洛赫:假设您允许子类访问您的内部数据结构,那么编写既可子类化又能抵抗恶意子类的东西实际上是一件非常困难的事情。如果子类无法访问普通用户无法访问的任何内容,那么子类就更难造成损害。但是,除非您将所有方法都设置为最终方法,否则子类仍然可以通过对方法调用做出错误的事情来破坏您的契约。这正是像 String 这样的安全关键类是 final 的原因。否则,有人可以编写一个子类,使字符串看起来是可变的,这足以破坏安全性。所以你必须信任你的子类。如果你不信任他们,那么你就不能允许他们,

就一般受保护的数据而言,这是一种必要的邪恶。它应该保持在最低限度。大多数受保护的数据和受保护的方法相当于对实现细节的承诺。受保护字段是您使子类可见的实现细节。即使是受保护的方法也是您使子类可见的内部结构。

你让它可见的原因是它通常是必要的,以便允许子类完成它们的工作,或者有效地完成它。但是一旦你完成了它,你就会致力于它。它现在是不允许更改的,即使您以后找到不再涉及使用特定字段或方法的更有效的实现也是如此。

因此,在所有其他条件相同的情况下,您根本不应该有任何受保护的成员。但是就是说,如果您的课程太少,那么您的课程可能无法用作超课程,或者至少不能用作高效的超课程。通常你会在事后发现。我的理念是在你第一次编写类时尽可能少地包含受保护的成员。然后尝试对其进行子类化。您可能会发现,如果没有特定的受保护方法,所有子类都将不得不做一些坏事。

例如,如果您查看 AbstractList,您会发现有一种受保护的方法可以一次性删除列表的范围(removeRange)。为什么会在里面?因为基于公共 API 删除范围的正常习惯用法是调用subList以获取子对象List,然后调用clear该子对象List。然而,如果没有这种特殊的受保护方法,唯一clear可以做的就是反复删除单个元素。

想想看。如果你有一个数组表示,它会做什么?它会反复折叠数组,做 N 次工作 N 次。因此,它将需要二次工作量,而不是它应该做的线性工作量。通过提供这种受保护的方法,我们允许任何可以有效删除整个范围的实现这样做。并且任何合理List的实现都可以一次更有效地删除一个范围。

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

Bill Venners: 还有受保护的数据?

Josh Bloch:同样的事情,但更多。就弄乱数据不变量而言,受保护的数据更加危险。如果您让其他人访问某些内部数据,他们就可以自由支配这些数据。

简短的版本:它打破了封装,但它是一个必要的邪恶,应该保持在最低限度。

于 2009-10-09T23:41:06.967 回答
8

C#:

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

于 2008-09-16T19:52:27.893 回答
7

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

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

于 2008-09-16T19:51:36.723 回答
4

Scott Meyers 说不要在 Effective C++(第 3 版)中使用受保护的属性:

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

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

于 2008-09-16T19:59:02.870 回答
2

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

于 2008-09-16T19:51:37.147 回答
2

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

于 2008-09-16T19:52:46.330 回答
2

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

[+] 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(即:默认使用private,当您必须公开该方法时使用public)。

于 2008-09-17T20:35:37.327 回答
2

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

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

于 2016-04-16T05:26:07.880 回答
1

一般来说,不,您真的不想使用受保护的数据成员。如果您编写 API,则更是如此。一旦有人从你的类继承,你就永远不能真正进行维护,也不能以一种奇怪的,有时甚至是狂野的方式破坏它们。

于 2008-09-16T19:53:37.763 回答
1

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

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

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

于 2008-09-16T19:53:57.967 回答
1

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

于 2008-09-16T19:54:22.427 回答
1

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

在使用中,通过对一个类的所有子级共享的对象使用受保护的final变量可以提供一定程度的简单性。我总是建议不要将它与原语或集合一起使用,因为无法为这些类型定义合同。

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

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

这不是什么愚蠢的捷径,我非常认真地考虑了一下,并决定公开最终变量公开对象和吸气剂之间绝对没有区别。

于 2008-09-16T19:58:18.893 回答
1

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

如果您的用户弄乱了您的课程,那不是您的问题。每个恶意用户都可以在覆盖您的一个虚拟对象时添加以下行:

(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;}
}

这是我个人的看法。

但是做你老板要求的(如果他想要私人领域,那就这样做。)

于 2010-01-09T16:51:57.527 回答
1

我在我知道不打算更改为方法的基类中使用受保护的变量/属性。这样,子类可以完全访问它们继承的变量,并且没有通过 getter/setter 访问它们的(人为创建的)开销。一个示例是使用底层 I/O 流的类;几乎没有理由不允许子类直接访问底层流。

这对于在基类和所有子类中以直接简单的方式使用的成员变量很好。但是对于具有更复杂用途的变量(例如,访问它会导致类中的其他成员产生副作用),直接访问的变量是不合适的。在这种情况下,可以将其设为私有,而可以提供公共/受保护的 getter/setter。一个例子是基类提供的内部缓冲机制,直接从子类访问缓冲区会损害基类用来管理它们的算法的完整性。

这是一个设计判断决定,基于成员变量的简单程度以及在未来版本中的预期程度。

封装很棒,但它可能太过分了。我见过自己的私有方法仅使用 getter/setter 方法访问其成员变量的类。这太过分了,因为如果一个类不能用自己的私有数据信任自己的私有方法,它还能信任谁呢?

于 2010-01-09T17:07:31.453 回答