/**
* 献给我最尊敬的偶像Martin Fowler
* 原文出处:https://martinfowler.com/bliki/SelfEncapsulation.html
* @author dogstar.huang <chanzonghuang@gmail.com> 2017-03-17
*/
本翻译已征得Martin Fowler同意,并链接在博客原文下方。
自封装
数据封装是面向对象风格的核心原则。这表明一个对象的字段不应该公开地暴露,而是来自对象以外的全部访问都应该通过访问器方法(getter和setter)。有些语言允许公开访问的字段,但我们通常提醒程序员不要这样做。自封装(Self-encapsulation)更进了一步,意味着全部对于数据字段的内部访问同样也应该通过访问器方法。只有访问器方法本身才能接触到这些数据变量。如果数据字段不暴露给外部,这就意味着要添加额外的私有访问器。
以下是一个合理封装的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()); }
自封装意味着getAmounts
需要通过getter访问这两个字段。这也意味着我需要为rate添加一个getter,并且设置为私有方法。
封装不可变数据通常是一个好主意。更新方法可以包含执行验证和重要逻辑的代码。通过约束用函数来访问,我们可以支持统一访问原则,使得我们可以隐藏计算了哪些数据和存储了哪些数据。这些访问器允许我们在保持相同公开接口的同时修改数据结构。在关于对于一个对象什么是“外部(outside)”,根据各种各样的访问修饰符,不同的语言在细节上会有所不同,但大部分环境都支持某种程度上的数据封装。
我遇到过一些强制自封装的组织,并且自从90年代后用不用它就成了一个常规辩论的主题。它的提倡者说自封装是这么一个好处,你也会想把它结合到内部访问。批评家则认为它是导致掩盖发生了什么的多余代码的无用仪式。
对于这点,我的观点是大多数情况下自封装有一点价值。封装的价值与数据访问的作用域成正比。类通常很小(至少我的是),所以在那样的作用域内直接访问不会有问题。大多数访问器都是简单赋值的setter和提取的getter,所以内部地使用自封装价值很小。
但也有值得为自封装付出努力的通用情况。如果有逻辑在setter里,那么为全部内部的更新也考虑这一点是很明智的。另一种情况是当类是继承结构中的一部分时,访问器为子类提供有用的钩子以便重载行为。
所以通常我的第一举动是直接访问字段,但对于需要自封装的情况则使用自封装字段进行重构。通常,可以使用提取一个新的类来解决导致我考虑使用自封装的压力。
扩展阅读
在实现模式和[Smalltalk最侍实践模式]里,Kent Beck讨论了直接访问(Direct Access)和间接访问(Indirect Access)之间的权衡。
致谢
Ian Cartwright,Matteo Vaccari,和Philip Duldig在这篇文章的草稿上进行了评论。
------------------------
本作品采用知识共享署名-非商业性使用-相同方式共享 3.0 未本地化版本许可协议进行许可。
- 本文翻译作者为:dogstar,发表于艾翻译(itran.cc);欢迎转载,但请注明出处,谢谢!