何时应该使用封装?

3
我正在完成Sun/Oracle的Trail(http://docs.oracle.com/javase/tutorial/java/TOC.html),它一直强调封装的重要性。
实际上,封装有多重要呢?我的意思是,如果我需要访问给定类字段的值,为什么不直接访问该字段,而要通过一个方法来访问?既然该字段将通过其对应的对象访问,那么这样做有什么问题吗?
这只是为了代码可扩展性而设计的吗?换句话说,因为这样在将来,如果我决定在返回之前对字段进行修改或清理,那么我可以这样做?
我更想看一两个例子。

1
封装不仅仅是指对字段的私有/公共访问。请查看维基百科文章 - Sotirios Delimanolis
你能再给我举个这个术语的用法例子吗?谢谢! - J S
1
在维基链接中,查看“信息隐藏”。 - Sotirios Delimanolis
啊,很抱歉,我不知怎么就没有看到维基百科的文章。 - J S
好的,我看了。既然Java是在客户端运行的,如果他们决定动手脚,真的会有什么危害吗?是因为这通常会使找到可利用的漏洞更容易吗? - J S
4个回答

3

验证。

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

它还将类的数据与外部世界分离。通过隐藏方法背后的实际数据实现,您可以按照自己的方式(现在和未来)操作数据,并且没有其他代码会出现问题。只要确保仍然可以通过现有方法返回,就可以更改某些内容的表示方式而不会出现问题。


好的。我实际上是在我的原始帖子中添加了这个,因为我忘记提到它,并且至少认识到它的价值,可以防止/减轻错误发生时的损害。还有其他遵守这种做法的理由吗? - J S
当然,我稍微扩展了一下。 - Jeroen Vannevel
1
在某些情况下,如果类本身实现了长时间运行的进程,也可以通过拒绝访问来更好地确保数据完整性,以防止在进程运行时更改值。这是验证案例的一个子集,但非常重要。 - SplinterReality

3
封装不仅仅是为字段创建getter和setter。
它还涉及以下几个方面:
- 验证(以及一致性) - 隐藏实现(编程接口而非实现) - getter和setter不必反映实际字段。可能会有用于按需计算值的getter(甚至setter)。 - 隐藏复杂性:getter/setter可以执行比设置值更复杂的操作。 - 高级选项:使用不同的实现/修改;像ORM框架中使用的延迟加载模式,如果使用公共字段,则无法工作。
即使你需要像你说的“访问给定类字段的值”,你也不能确定这个要求不会改变(因为它大多数时候都会改变)。

1
实际上,我认为您的思路有些偏差。问题不在于封装本身,而在于将对象的行为与数据分离开来。
字段是数据——它们是对象的内部状态的一部分。方法是对象的 API 的一部分。对象不应该只是字段的集合——如果您的对象只是数据的愚蠢集合,那么这不是面向对象编程,而只是结构化编程。
对象应该被设计成代表现实世界中的实体,并具有表示对这些实体进行操作的方法。换句话说,您不应该要求对象提供其字段(例如 getFoo()、getBar())以将其传递给其他函数——相反,您应该直接将相关操作(例如 purchase()、validate() 等)作为方法放在对象上。

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

基本上,在Java(或任何面向对象的语言)中,类是名词,方法是动词。如果您编写的类没有任何动词,那么您就是在使用名词编程


0

封装允许您的对象通过控制自己的数据(这恰好使得调试更加容易)来做出承诺(对象的一部分“合同”)。考虑以下类:

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异常,提供一个信息丰富的堆栈跟踪,立即暴露罪犯。

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


这并不是不可能的,使用反射仍然可以很容易地更改类外部的变量。现在,你整篇文章的推理都是无效的。 - Esailija
试图访问另一个类的私有变量会导致IllegalAccessException,除非有人在Field上实际调用setAccessible(true),这只有在没有SecurityManager时才可能发生。如果有人正在调用setAccessible,则他们不是在编写应用程序,而是在黑客攻击——此时,说“如果调用setAccessible(true)则可以完成”就像说“如果有人修改了您的字节码或在修改后的JVM中运行,则可以完成”。强制破解或黑客攻击始终是可能的。 - VGR
默认情况下没有安全管理器,需要运行应用程序的人来设置它,而不是开发人员。这绝对不是黑客攻击,而是在大多数应用程序中都可以实现的完全正常的情况。 - Esailija
在Java EE容器中,总是安装了SecurityManager。如果您认为使用setAccessible是良好的开发实践,那么您应该搜索StackOverflow以查找所有声称java.lang.String是不可变的答案,并将其投票否决。 - VGR
我已经厌倦了与那些不理解安全性和封装之间区别或认为它们是同一回事的Java开发人员争论。 - Esailija

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接