【问题标题】:Should ecapsulated objects be public or private?封装的对象应该是公共的还是私有的?
【发布时间】:2011-09-12 00:09:57
【问题描述】:

对于将类中的所有成员设为私有并设为公共方法来处理突变的想法,我有点不清楚。原始类型不是问题,它是我不清楚的封装对象。将对象成员设为私有的好处是能够隐藏不适用于正在构建的类的上下文的方法。缺点是您必须提供公共方法来将参数传递给底层对象(更多方法,更多工作)。另一方面,如果你想让底层对象的所有方法和属性都暴露出来,你不能把对象公开吗? 以这种方式暴露物体有什么危险?

例如,我会发现将向量或数组列表中的所有内容都暴露出来很有用。我能想到的唯一缺点是公共成员可能会分配一个类型,它不是通过隐式转换(或其他影响的东西)。一个易变的名称会减少问题的可能性吗?

附带说明:我理解真正的封装意味着成员是私有的。

【问题讨论】:

  • 参见得墨忒耳定律en.wikipedia.org/wiki/Law_of_Demeter,在 OOP 语言中,您应该尽量减少依赖关系。这取决于“类”是否真正具有功能,或者实际上是一种数据结构。如果它只是一个数据结构,那么 getter 'n' setter 似乎有点矫枉过正,特别是如果所有字段都可以是不可变的(最终)。

标签: oop class-design encapsulation


【解决方案1】:

以这种方式暴露物体有什么危险?

更改这些对象的类型需要更改类的接口。使用私有对象 + 公共 getter/setter,您只需修改 getter 和 setter 中的代码,假设您希望保持返回的内容相同。

请注意,这就是为什么属性在 Python 等语言中很有用的原因,从技术上讲,Python 没有私有类成员,最多只有隐藏的成员。

【讨论】:

  • 但是你不认为这是声明private objects + public getters/setters 的结果吗?如果他宣布一切都是公开的呢?尽管由于许多其他原因而不可取,但我认为答案不能证明问题的合理性
  • “如果他把所有东西都公开怎么办?”然后没有封装,并且更改直接公开的类的成员将需要更改访问该直接公开的成员并期望它以某种方式运行的任何内容。你想得有点倒退了。
【解决方案2】:

将实例变量公开的问题在于,您以后永远无法改变主意,将它们设为私有,而不会破坏依赖于直接公开访问这些实例变量的现有代码。一些例子:

  • 您决定稍后通过同步对实例变量的所有访问来使您的类线程安全,或者可能通过使用 ThreadLocal 为每个线程创建值的新副本。如果任何线程可以直接访问变量,则不能这样做。

  • 使用向量或数组列表的示例 - 在某些时候,您会意识到代码中存在安全漏洞,因为这些类是可变的,因此其他人可以替换列表的内容。如果这只能通过访问器方法获得,您可以通过根据请求制作列表的不可变副本来轻松解决问题,但您不能使用公共变量来做到这一点。

  • 稍后您会意识到您的实例变量之一是多余的,并且可以基于其他变量派生。同样,如果您使用访问器,这很容易,而使用公共变量则不可能。

我认为这归结为一个实际点 - 如果您知道您是唯一将使用此代码的人,并且编写访问器会让您感到痛苦(每个 IDE 都会自动为您完成),并且如果您决定破坏 API,您不介意稍后更改您自己的代码,然后继续这样做。但是,如果其他人将使用您的类,或者您希望以后更容易重构以供自己使用,请坚持使用访问器。

【讨论】:

    【解决方案3】:

    面向对象的设计只是一个指导方针。从将使用您的课程的人的角度考虑它。平衡 OOD 与使其直观且易于使用。

    【讨论】:

    • 说得好。然后你会希望你的命名约定与类的设计一致。所以如果你有一个公共的整数向量来保存素数,我想给一个像“PrimeVector”这样的名字会很糟糕
    • 显然您想将其称为“VectorPrime”。
    • 这么多取决于您手头任务的细节。例如,如果我们在谈论 Java,其中 Vector 是一个众所周知的类并且您正在设计该类以供 Java 程序使用,那么我的偏好是将其命名为“ PrimeNumbers" 和 Vector 类型。如果您使用不同的语言,在其中滚动了自己的名为 Vector 的类,则还需要在界面中公开该类。如果你正在做一个不是强类型的接口,我会称之为“PrimeNumbersVector”。此外,如果您正在编写 COM 接口,还有其他注意事项。
    • @JAB 要是有 Optimus 课就好了。 ;-)
    • Brighwell - COM 参考的上调。
    【解决方案4】:

    您可能会遇到问题,具体取决于您使用的语言以及它如何处理返回语句或赋值运算符。在某些情况下,它可能会为您提供参考,或在其他情况下为您提供价值。

    例如,假设您有一个计算素数的 PrimeCalculator 类,然后您有另一个类来处理这些素数。

    public PrimeCalculator calculatorObject = new PrimeCalculator();
    
    Vector<int> primeNumbers = calculatorObject.PrimeNumbersVector;
    /* do something complicated here */
    primeNumbers.clear(); // free up some memory
    

    当你以后使用这个东西时,可能在另一个类中,你不希望再次计算数字的开销,所以你使用相同的calculatorObject。

    Vector<int> primes = calculatorObject.PrimeNumbersVector;
    int tenthPrime = primes.elementAt(9);
    

    此时可能并不完全清楚 primes 和 primeNumbers 是否引用同一个 Vector。如果这样做,尝试从素数中获取第十个素数会引发错误。

    如果您小心并了解您的具体情况,您可以这样做,但使用函数返回值而不是直接分配变量的误差范围较小。

    【讨论】:

      【解决方案5】:

      你可以查看帖子:

      first this

      then this

      这应该可以解决您的困惑。 它解决了我的问题!感谢Nicol Bolas。

      还请阅读已接受答案下方的 cmets (还请注意我在倒数第二条评论中给出的链接(在第一篇文章中))

      也请访问the wikipedia post

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-14
        • 1970-01-01
        • 2014-06-18
        • 1970-01-01
        • 1970-01-01
        • 2022-12-07
        相关资源
        最近更新 更多