【问题标题】:When should encapsulation be used? [closed]什么时候应该使用封装? [关闭]
【发布时间】:2013-12-24 00:49:38
【问题描述】:

我正在完成 Sun/Oracle 的 Trail (http://docs.oracle.com/javase/tutorial/java/TOC.html),它不断重申封装的重要性。

封装到底有多重要?我的意思是,如果我可能需要访问给定类字段的值,那么当我可以直接访问该字段时,为什么还要通过一种方法呢?由于无论如何该字段都将通过其相应的对象进行访问,这真的会出错吗?

只是为了代码可扩展性的目的吗?换句话说,因为那样的话,如果我决定在返回之前以某种方式改变或清理该字段,我可以吗?

我更多的是寻找一两个例子。

【问题讨论】:

  • 封装不仅仅指对字段的私有/公共访问。通过wiki article。
  • 您能否再举一个例子说明如何使用该术语?谢谢!
  • 在维基链接中,查看information hiding。
  • 啊,对不起,我以某种方式浏览了维基文章。
  • 好的,我读到了。鉴于 Java 在客户端运行,如果他们决定修改它,真的有什么危害?是因为它通常更容易找到可利用的弱点吗?

标签: java oop encapsulation


【解决方案1】:

验证。

如果您不使用方法,则无法在该字段上添加任何验证,除非您在要访问该字段的每个位置都对其进行验证:不可持续。

它还将您班级的数据与外界分开。通过将数据的实际实现隐藏在方法后面,您可以按照您想要的方式(现在和将来)操作数据,并且不会破坏其他代码片段。这使您可以毫无问题地更改某些事物的表示方式,只要您确保它仍然可以通过现有方法返回。

【讨论】:

  • 好的。实际上,我将其添加到我的原始帖子中是因为我忘记提及它,并且至少承认该价值,作为发生错误时的损害预防/减轻。还有其他理由坚持这种做法吗?
  • 当然,我扩展了一点。
  • 如果您有一个类本身实现的长时间运行的进程,它还可以通过在进程旋转时拒绝更改值的访问来帮助确保数据完整性。这是验证案例的一个子集,但非常重要。
【解决方案2】:

封装不仅仅是为一个字段制作getter和setter。

关于:

  • 验证(以及一致性)
  • 隐藏实现(编程到接口而不是实现)
  • getter 和 setter 不必反映实际字段。对于值是按需计算
  • 的字段,可能会有getter(甚至setter)
  • 隐藏复杂性:getter/setter 可以执行比设置值更复杂的操作
  • 高级:使用不同的实现/修改;如果您使用公共字段,ORM 框架中使用的 延迟加载 等模式将不起作用

即使你说“需要访问给定类字段的值”,你也不能确定这个要求不会改变(因为大多数时候都会改变)。

【讨论】:

    【解决方案3】:

    实际上,我认为您的想法是错误的。问题不在于封装本身,而是将对象的行为与其数据解耦。

    字段是数据——它们是对象内部状态的一部分。方法是对象的API 的一部分。对象不应该只是字段的集群——如果您的对象只是数据的愚蠢集合,那么这不是面向对象的编程,而只是结构化编程。

    对象应该被设计为代表现实世界的实体,并具有代表您可以对这些现实世界实体进行的操作的方法。换句话说,您不要询问对象的字段(例如getFoo()、getBar())以将它们传递给其他函数——相反,您应该放置相关操作(例如purchase()、validate() 等)直接作为对象上的方法。

    也就是说,拥有访问器方法并没有错——有时您确实需要实际检索值。但是通过使这些访问器方法 而不是直接公开字段,您正在实现信息隐藏:您的类的用户不需要知道内部状态是什么样的能够使用它或从中获取数据。

    基本上,在 Java(或任何面向对象的语言)中,类是名词,方法是动词。如果你编写的类没有任何动词,那么你就是programming in the kingdom of nouns。

    【讨论】:

      【解决方案4】:

      封装允许您的对象通过让对象控制自己的数据来制作保证(对象的合同的一部分),这恰好使调试变得相当容易。考虑这个类:

      public class TravelRoute {
          public int distance = 1000;
          public int travelSpeed = 60;
      
          public int calculateTravelTime() {
              return distance / travelSpeed;
          }
      }
      

      任何其他代码都可以将travelSpeed 设置为零,这将导致以后对calculateTravelTime 方法的所有调用都失败。更糟糕的是,您将无法知道是谁将其设置为零,因此调试问题需要很长时间。

      但是,通过封装,类可以完全控制值,并且可以保证它始终有效:

      public class TravelRoute {
          private int distance = 1000;
          private int travelSpeed = 60;
      
          /**
           * This is GUARANTEED to return a positive value.
           */
          public int getTravelSpeed() {
              return travelSpeed;
          }
      
          /**
           * Sets this instance's travel speed.
           *
           * @throws IllegalArgumentException if argument is not positive
           */
          public void setTravelSpeed(int newSpeed) {
              if (newSpeed <= 0) {
                  throw new IllegalArgumentException("Argument must be positive");
              }
              this.travelSpeed = newSpeed;
          }
      
          public int calculateTravelTime() {
              return distance / travelSpeed;
          }
      }
      

      现在任何外部代码都绝对不可能将对象置于无效状态。如果有人试图这样做,生成的 IllegalArgumentException 将为您提供信息丰富的堆栈跟踪,该跟踪将立即暴露罪魁祸首。

      作为奖励,使用该类的所有其他代码不再需要对其有效性进行任何检查,因为对象本身已经可以保证该有效性。这使得每个人的整体开发速度都更快。

      【讨论】:

      • 这不是不可能的,用反射改变类外的变量还是很简单的。现在你整个帖子中的推理是无效的。
      • 尝试访问另一个类的私有变量会导致 IllegalAccessException,除非有人实际在 Field 上调用 setAccessible(true),这只有在没有 SecurityManager 时才有可能。如果有人在调用 setAccessible,他们并不是在编写应用程序,而是在进行黑客攻击——此时,说“如果调用 setAccessible(true) 就可以完成”就像说“如果有人修改了您的字节码就可以完成,或者在修改后的 JVM 中运行。”强行破坏或破解某些东西总是有可能的。
      • 默认情况下没有安全管理器,这取决于运行应用程序的人需要设置它——而不是开发人员。这绝对不是黑客攻击,而是在大多数应用程序中可行的完全正常情况..
      • 在 Java EE 容器中总是安装了一个 SecurityManager。如果您认为使用 setAccessible 是一种良好的开发实践,您应该在 StackOverflow 中搜索所有声称 java.lang.String 是不可变的答案,并对其进行否决。
      • 我厌倦了与不了解安全性和封装性之间的区别或认为它们是同一事物的 Java 开发人员争论。
      猜你喜欢
      • 2017-09-11
      • 1970-01-01
      • 2012-12-23
      • 2021-07-13
      • 2022-01-02
      • 2011-07-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多