关于oop:你应该使用受保护的成员变量吗?

关于oop:你应该使用受保护的成员变量吗?

Should you ever use protected member variables?

你应该使用受保护的成员变量吗? 有什么优势,这会带来什么问题?


Should you ever use protected member variables?

取决于你对隐藏状态的挑剔程度。

  • 如果您不希望任何内部状态泄漏,那么将所有成员变量声明为私有是最佳选择。
  • 如果你真的不在乎子类可以访问内部状态,那么受保护就足够了。

如果开发人员出现并将您的类子类化,他们可能会搞砸,因为他们不完全理解它。对于除公共界面之外的私有成员,他们无法看到实现方式的实现具体细节,这使您可以灵活地在以后更改它。


现在普遍的感觉是它们导致派生类和它们的基础之间的过度耦合。

它们与受保护的方法/属性相比没有特别的优势(曾几何时它们可能具有轻微的性能优势),并且它们在更深层次的继承已经流行的时代也被更多地使用,而现在还没有。


一般来说,如果某些东西不是故意设想为公开的,我会把它私有化。

如果出现需要从派生类访问该私有变量或方法的情况,我将其从private更改为protected。

这几乎不会发生 - 我真的不是所有继承的粉丝,因为它不是模拟大多数情况的特别好的方法。无论如何,继续,不用担心。

对于大多数开发人员来说,我说这很好(也许是最好的方法)。

问题的简单事实是,如果其他开发人员一年后出现并决定他们需要访问您的私有成员变量,他们只需编辑代码,将其更改为受保护,并继续他们的业务。

唯一真正的例外情况是,如果您的业务是以黑盒形式将二进制dll发送给第三方。这基本上包括Microsoft,那些"Custom DataGrid Control"供应商,以及可能带有可扩展性库的其他一些大型应用程序。除非你属于那个类别,否则不值得花时间/精力去担心这种事情。


在设计级别,使用受保护的属性可能是合适的,但是对于实现,我认为将其映射到受保护的成员变量而不是accessor / mutator方法没有任何优势。

受保护的成员变量具有明显的缺点,因为它们有效地允许客户端代码(子类)访问基类类的内部状态。这可以防止基类有效地维护其不变量。

出于同样的原因,受保护的成员变量也使编写安全的多线程代码变得更加困难,除非保证不变或局限于单个线程。

Accessor / mutator方法在维护时提供了更多的API稳定性和实现灵活性。

此外,如果您是OO纯粹主义者,则对象通过发送消息进行协作/通信,而不是读取/设置状态。

作为回报,它们提供的优势很少。我不一定会从其他人的代码中删除它们,但我不会自己使用它们。


一般情况下,我会将受保护的成员变量保留在极少数情况下,您可以完全控制使用它们的代码。如果您正在创建公共API,我会说永远不会。下面,我们将成员变量称为对象的"属性"。

这是你的超类在使成员变量受保护而不是私有访问者后不能做的事情:

  • 在阅读该属性时,懒惰地创建一个值。如果添加受保护的getter方法,则可以懒惰地创建值并将其传回。

  • 知道何时修改或删除了该属性。当超类对该变量的状态做出假设时,这可能会引入错误。为变量创建受保护的setter方法可以保持该控制。

  • 在读取或写入变量时设置断点或添加调试输出。

  • 重命名该成员变量,而不搜索可能使用它的所有代码。

  • 一般来说,我认为我建议制作受保护的成员变量是极少数情况。最好花几分钟时间通过getter / setter暴露财产,而不是几小时后追踪修改受保护变量的其他代码中的错误。不仅如此,您还可以避免在不破坏相关代码的情况下添加未来的功能(例如延迟加载)。现在做这件事要比现在做得更难。


    对我来说,关键问题是,一旦你对变量进行了保护,你就不能允许你的类中的任何方法依赖于它在一个范围内的值,因为子类总是可以将它放在范围之外。

    例如,如果我有一个定义可渲染对象的宽度和高度的类,并且我使这些变量受到保护,那么我就不能对(例如)宽高比做出任何假设。

    至关重要的是,从代码作为库发布的那一刻起,我就永远无法做出这些假设,因为即使我更新我的setter以保持宽高比,我也无法保证变量是通过setter设置的,也可以通过现有代码中的getter。

    我的类的任何子类也不能选择保证,因为它们也不能强制执行变量值,即使这是它们子类的整个点。

    举个例子:

    • 我有一个宽度和高度的矩形类存储为受保护的变量。
    • 一个明显的子类(在我的上下文中)是一个"DisplayedRectangle"类,唯一的区别是我将宽度和高度限制为图形显示的有效值。
    • 但现在这是不可能的,因为我的DisplayedRectangle类不能真正约束这些值,因为它的任何子类都可以直接覆盖这些值,同时仍被视为DisplayedRectangle。

    通过将变量约束为私有,我可以通过setter或getter强制执行我想要的行为。


    大多数情况下,使用受保护是危险的,因为你打破了类的封装,这很可能被设计不良的派生类分解。

    但我有一个很好的例子:假设您可以使用某种通用容器。它有一个内部实现和内部访问器。但是你需要提供至少3次公共访问它的数据:map,hash_map,vector-like。然后你有类似的东西:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    template <typename T, typename TContainer>
    class Base
    {
       // etc.
       protected
       TContainer container ;
    }

    template <typename Key, typename T>
    class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

    template <typename Key, typename T>
    class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

    template <typename T>
    class DerivedVector  : public Base<T, std::vector< T > >        { /* etc. */ }

    我在不到一个月前使用过这种代码(因此代码来自内存)。经过一番思考后,我相信虽然泛型Base容器应该是一个抽象类,即使它可以很好地存活,因为直接使用Base会是一种应该被禁止的痛苦。

    总结因此,您拥有派生类使用的受保护数据。尽管如此,我们必须考虑到基类应该是抽象的事实。


    只是为了记录,在"Exceptional C ++"的第24项下,在其中一个脚注中,Sutter说
    "你永远不会写一个有公共或受保护成员变量的类。对吧?(不管一些图书馆设置的不好的例子。)"


    简而言之,是的。

    受保护的成员变量允许从任何子类以及同一个包中的任何类访问该变量。这非常有用,特别是对于只读数据。我不相信它们是必要的,因为任何使用受保护的成员变量都可以使用私有成员变量和几个getter和setter进行复制。


    有关.Net访问修饰符的详细信息,请转到此处

    受保护的成员变量没有真正的优点或缺点,这是您在特定情况下需要的问题。通常,将成员变量声明为私有并通过属性启用外部访问是公认的惯例。此外,一些工具(例如一些O / R映射器)期望对象数据由属性表示,并且不识别公共或受保护的成员变量。但是,如果您知道您希望子类(并且只是您的子类)访问某个变量,则没有理由不将其声明为受保护。


    推荐阅读