关于oop:是否应该禁止受保护的属性?

关于oop:是否应该禁止受保护的属性?

Should protected attributes always be banned?

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

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


在Bill Venners的设计访谈中,Effective Java的作者Joshua Bloch说:

Trusting Subclasses

Bill Venners: Should I trust subclasses more intimately than
non-subclasses? For example, do I make
it easier for a subclass
implementation to break me than I
would for a non-subclass? In
particular, how do you feel about
protected data?

Josh Bloch: To write something that is both subclassable and robust
against a malicious subclass is
actually a pretty tough thing to do,
assuming you give the subclass access
to your internal data structures. If
the subclass does not have access to
anything that an ordinary user
doesn't, then it's harder for the
subclass to do damage. But unless you
make all your methods final, the
subclass can still break your
contracts by just doing the wrong
things in response to method
invocation. That's precisely why the
security critical classes like String
are final. Otherwise someone could
write a subclass that makes Strings
appear mutable, which would be
sufficient to break security. So you
must trust your subclasses. If you
don't trust them, then you can't allow
them, because subclasses can so easily
cause a class to violate its
contracts.

As far as protected data in general,
it's a necessary evil. It should be
kept to a minimum. Most protected data
and protected methods amount to
committing to an implementation
detail. A protected field is an
implementation detail that you are
making visible to subclasses. Even a
protected method is a piece of
internal structure that you are making
visible to subclasses.

The reason you make it visible is that
it's often necessary in order to allow
subclasses to do their job, or to do
it efficiently. But once you've done
it, you're committed to it. It is now
something that you are not allowed to
change, even if you later find a more
efficient implementation that no
longer involves the use of a
particular field or method.

So all other things being equal, you
shouldn't have any protected members
at all. But that said, if you have too
few, then your class may not be usable
as a super class, or at least not as
an efficient super class. Often you
find out after the fact. My philosophy
is to have as few protected members as
possible when you first write the
class. Then try to subclass it. You
may find out that without a particular
protected method, all subclasses will
have to do some bad thing.

As an example, if you look at
AbstractList, you'll find that there
is a protected method to delete a
range of the list in one shot
(removeRange). Why is that in there?
Because the normal idiom to remove a
range, based on the public API, is to
call subList to get a sub-List,
and then call clear on that
sub-List. Without this particular
protected method, however, the only
thing that clear could do is
repeatedly remove individual elements.

Think about it. If you have an array
representation, what will it do? It
will repeatedly collapse the array,
doing order N work N times. So it will
take a quadratic amount of work,
instead of the linear amount of work
that it should. By providing this
protected method, we allow any
implementation that can efficiently
delete an entire range to do so. And
any reasonable List implementation
can delete a range more efficiently
all at once.

That we would need this protected
method is something you would have to
be way smarter than me to know up
front. Basically, I implemented the
thing. Then, as we started to subclass
it, we realized that range delete was
quadratic. We couldn't afford that, so
I put in the protected method. I think
that's the best approach with
protected methods. Put in as few as
possible, and then add more as needed.
Protected methods represent
commitments to designs that you may
want to change. You can always add
protected methods, but you can't take
them out.

Bill Venners: And protected data?

Josh Bloch: The same thing, but even more. Protected data is even more
dangerous in terms of messing up your
data invariants. If you give someone
else access to some internal data,
they have free reign over it.

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


C#:

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


您可能需要它们用于静态(或"全局")属性,您希望您的子类或来自相同包的类(如果它是关于Java)受益。

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


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

Item 22: Declare data members private.

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


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

它不是破坏封装的受保护成员,它暴露了不应暴露的成员破坏了封装...它们是受保护还是公共无关紧要。 protected的问题在于它是错误的和误导性的...声明成员protected(而不是private)不能保护它们,它正好相反,完全和public一样。受保护的成员可以在类外部访问,它暴露给世界,所以它的语义必须永远保持,就像public的情况一样。"受保护"的整个想法是无稽之谈......封装不是安全性,关键字只会加剧两者之间的混淆。您可以通过避免在您自己的类中使用protected来帮助一点 - 如果某些内容是实现的内部部分,不是类的语义的一部分,并且可能在将来发生变化,然后将其设为私有或如果它是类语义的一个不可更改的部分,那么让它公开,然后你不会惹恼你的类的用户,他们可以看到文档中有一个有用的成员但是除非他们正在创建自己的实例并且可以通过子类化来实现它,否则不能使用它。


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


我最近在一个项目上工作过的"受保护"成员是一个非常好的主意。类层次结构是这样的:

1
2
3
4
5
6
7
8
9
[+] 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。我无法将这些"私有"放在那里,因为无论如何我都会通过读/写访问器公开所有或几乎所有的功能。

最后,我想如果你把你的类分成多个对象,每个派生都需要为它的母类添加所需的特性,并且只有最派生的类才真正可用,那么保护就是要走的路。事实上,"受保护的成员"是一个阶级,因此几乎不可能"打破",帮助。

但是,应尽可能避免受保护(即:默认情况下使用私有,并且必须公开方法时公开)。


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


通常,您真的不想使用受保护的数据成员。如果您编写API,这是双重的。一旦有人从你的班级继承,你就永远不会真正做到维护,也不会以某种奇怪的,有时是狂野的方式打破它们。


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


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

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

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

封装是很好的,但它可以采取太多。我见过自己的私有方法只使用getter / setter方法访问其成员变量的类。这是过度的,因为如果一个类不能信任自己的私有方法和它自己的私有数据,它可以信任谁?


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

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

(C#)

1
2
3
4
static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...

你不能密封每一堂课以防止这种情况发生。

当然,如果您想要对某些字段进行约束,那么请使用存取函数或属性或您想要的东西,并将该字段设为私有,因为没有其他解决方案......

但我个人不喜欢不惜一切代价坚持oop原则。特别是制作属性的唯一目的是使数据成员私有化。

(C#):

1
2
3
4
5
6
private _foo;
public foo
{
   get {return _foo;}
   set {_foo=value;}
}

这是我个人的意见。

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


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

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

最近,我将你用原型和原始集合与你用格式良好的类做的东西分开。原始和收藏品应始终是私人的。

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

这不是一些愚蠢的捷径,我认为它非常认真,并且决定暴露一个对象和一个getter的公共最终变量之间绝对没有区别。


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

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

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


推荐阅读