自我封装
2017年3月9日
数据封装是面向对象风格的核心原则。这意味着对象的字段不应该公开暴露,而是所有来自对象外部的访问都应该通过访问器方法(getter 和 setter)进行。有些语言允许公开访问字段,但我们通常建议程序员不要这样做。自我封装更进一步,表明所有内部对数据字段的访问也应该通过访问器方法进行。只有访问器方法应该接触数据值本身。如果数据字段没有暴露给外部,这意味着需要添加额外的私有访问器。
以下是一个合理封装的 Java 类示例
class Charge…
private int units; private double rate; public Charge(int units, double rate) { this.units = units; this.rate = rate; } public int getUnits() { return units; } public Money getAmount() { return Money.usd(units * rate); }
两个字段都是不可变的。units 字段通过 getter 暴露给类的客户端,但 rate 字段仅在内部使用,因此不需要 getter。
以下是用自我封装的版本。
class ChargeSE…
private int units; private double rate; public ChargeSE(int units, double rate) { this.units = units; this.rate = rate; } public int getUnits() { return units; } private double getRate() { return rate; } public Money getAmount() { return Money.usd(getUnits() * getRate()); }
自我封装意味着 getAmount
需要通过 getter 访问两个字段。这也意味着我必须为 rate
添加一个 getter,我应该将其设为私有。
封装可变数据通常是一个好主意。更新函数可以包含执行验证和后续逻辑的代码。通过限制函数的访问,我们可以支持统一访问原则,允许我们隐藏哪些数据是计算的,哪些是存储的。这些访问器允许我们在保留相同公共接口的同时修改数据结构。不同的语言在“外部”对于一个对象意味着什么方面有所不同,这取决于各种类型的访问修饰符,但大多数环境都支持某种程度的数据封装。
我遇到过一些强制使用自我封装的组织,从 90 年代开始,是否使用它一直是一个经常争论的话题。它的支持者说,封装是一个如此大的好处,以至于你想要将其纳入内部访问。批评者认为这是不必要的仪式,导致不必要的代码,掩盖了正在发生的事情。
我对这个问题的看法是,大多数情况下,自我封装几乎没有价值。封装的价值与数据访问的范围成正比。类通常很小(至少我的类是),因此直接访问在该范围内不会成为问题。大多数访问器都是 setter 的简单赋值和 getter 的检索,因此在内部使用它们几乎没有价值。
但有一些常见的情况,自我封装是值得的。如果 setter 中有逻辑,那么明智的做法是考虑将其用于任何内部更新。另一种情况是,当类是继承结构的一部分时,在这种情况下,访问器为子类提供了宝贵的挂钩点来覆盖行为。
因此,我通常的第一步是使用对字段的直接访问,但如果情况需要,则使用自我封装字段进行重构。通常导致我考虑自我封装的力量,我可以通过提取一个新类来解决。
进一步阅读
Kent Beck 在实现模式和Smalltalk 最佳实践模式中,分别以直接访问和间接访问的名义讨论了这些权衡。