封装不仅仅是为类定义访问器和修改器方法。它是面向对象编程的一个更广泛的概念,包括最小化类之间的相互依赖关系,它通常通过信息隐藏来实现。
封装的美妙之处在于在不影响用户的情况下改变事物的力量。
在像 Java 这样的面向对象编程语言中,您可以通过使用可访问性修饰符(public、protected、private,加上没有暗示包私有的修饰符)隐藏细节来实现封装。借助这些可访问性级别,您可以控制封装级别,级别限制越少,更改发生的成本就越高,并且该类与其他依赖类(即用户类和子类)的耦合度越高。
因此,目标不是隐藏数据本身,而是如何操作这些数据的实现细节。
我们的想法是提供一个公共接口,您可以通过该接口访问这些数据。您可以稍后更改数据的内部表示,而不会损害类的公共接口。相反,通过暴露数据本身,您损害了封装性,因此损害了在不影响其用户的情况下改变您处理数据的方式的能力。您创建对数据本身的依赖,而不是对类的公共接口。当“改变”终于找到你时,你会为麻烦调制一杯完美的鸡尾酒。
您可能希望封装对字段的访问权限有几个原因。 Joshua Bloch 在他的书 Effective Java 中的第 14 条:最小化类和成员的可访问性中提到了几个令人信服的原因,我在此引用:
- 您可以限制可以存储在字段中的值(即性别必须是 F 或 M)。
- 您可以在修改字段时执行操作(触发事件、验证等)。
- 您可以通过同步方法来提供线程安全。
- 您可以切换到新的数据表示形式(即计算字段、不同的数据类型)
但是,封装不仅仅是隐藏字段。在 Java 中,您可以隐藏整个类,从而隐藏整个 API 的实现细节。例如,在方法@987654324@ 中思考。它返回一个List 实现,但你不关心哪个实现,只要它满足List 接口,对吧?将来可以更改实现,而不会影响方法的用户。
封装之美
现在,在我看来,要理解封装,首先必须理解抽象。
例如,考虑汽车概念的抽象级别。汽车的内部实现很复杂。它们有几个子系统,如传动系统、制动系统、燃油系统等。
但是,我们已经简化了它的抽象,我们通过它们抽象的公共接口与世界上所有的汽车进行交互。我们知道所有的汽车都有一个方向盘,我们通过它来控制方向,它们有一个踏板,当你按下它时,你可以加速汽车并控制速度,还有一个当你按下它时,它就会停下来,你有一个档位可以让您控制前进或后退的棍子。这些特性构成了汽车抽象的公共接口。早上你可以开一辆轿车,然后下车,下午开一辆 SUV,就好像它是同一件事一样。
但是,我们中很少有人知道所有这些功能是如何在后台实现的。想想汽车没有液压方向系统的时代。有一天,汽车制造商发明了它,他们决定从那时起将其放入汽车中。尽管如此,这并没有改变用户与他们交互的方式。最多,用户体验到使用定向系统的改进。像这样的改变是可能的,因为汽车的内部实现是封装的。可以安全地进行更改,而不会影响其公共界面。
现在,想想汽车制造商决定将油箱盖放在汽车下方,而不是放在汽车的一侧。你去买一辆这样的新车,当你用完油时,你去加油站,却找不到油箱盖。突然你意识到它在汽车下方,但你无法用气泵软管到达它。现在,我们已经破坏了公共接口契约,因此,整个世界都崩溃了,它分崩离析,因为事情没有按照预期的方式工作。像这样的改变将花费数百万美元。我们需要更换世界上所有的气泵。当我们打破封装时,我们必须付出代价。
因此,如您所见,封装的目标是最大限度地减少相互依赖并促进变化。您可以通过最小化实现细节的暴露来最大化封装。类的状态只能通过其公共接口访问。
我真的建议您阅读 Alan Snyder 的一篇名为 Encapsulation and Inheritance in Object-Oriented Programming Languages 的论文。此链接指向 ACM 上的原始论文,但我很确定您可以通过 Google 找到 PDF 副本。