class Person {
get name() {...}
set name(aString) {...}
 
 
class Person {
get name() {...}

动机

如果为某个字段提供了设值函数,这就暗示这个字段可以被改变。如果不希望在对象创建之后此字段还有机会被改变,那就不要为它提供设值函数(同时将该字段声明为不可变)。这样一来,该字段就只能在构造函数中赋值,我“不想让它被修改”的意图会更加清晰,并且可以排除其值被修改的可能性——这种可能性往往是非常大的。

有两种常见的情况需要讨论。一种情况是,有些人喜欢始终通过访问函数来读写字段值,包括在构造函数内也是如此。这会导致构造函数成为设值函数的唯一使用者。若果真如此,我更愿意去除设值函数,清晰地表达“构造之后不应该再更新字段值”的意图。

另一种情况是,对象是由客户端通过创建脚本构造出来,而不是只有一次简单的构造函数调用。所谓“创建脚本”,首先是调用构造函数,然后就是一系列设值函数的调用,共同完成新对象的构造。创建脚本执行完以后,这个新生对象的部分(乃至全部)字段就不应该再被修改。设值函数只应该在起初的对象创建过程中调用。对于这种情况,我也会想办法去除设值函数,更清晰地表达我的意图。

做法

如果构造函数尚无法得到想要设入字段的值,就使用改变函数声明(124)将这个值以参数的形式传入构造函数。在构造函数中调用设值函数,对字段设值。

如果想移除多个设值函数,可以一次性把它们的值都传入构造函数,这能简化后续步骤。

移除所有在构造函数之外对设值函数的调用,改为使用新的构造函数。每次修改之后都要测试。

如果不能把“调用设值函数”替换为“创建一个新对象”(例如你需要更新一个多处共享引用的对象),请放弃本重构。

使用内联函数(115)消去设值函数。如果可能的话,把字段声明为不可变。

测试。

范例

我有一个很简单的 Person 类。

class Person…

get name() {return this._name;}
set name(arg) {this._name = arg;}
get id() {return this._id;}
set id(arg) {this._id = arg;}

目前我会这样创建新对象:

const martin = new Person();
martin.name = "martin";
martin.id = "1234";

对象创建之后,name 字段可能会改变,但 id 字段不会。为了更清晰地表达这个设计意图,我希望移除对应 id 字段的设值函数。

但 id 字段还得设置初始值,所以我首先用改变函数声明(124)在构造函数中添加对应的参数。

class Person…

constructor(id) {
  this.id = id;
}

然后调整创建脚本,改为从构造函数设值 id 字段值。

const martin = new Person("1234");
martin.name = "martin";
martin.id = "1234";

所有创建 Person 对象的地方都要如此修改,每次修改之后要执行测试。

全部修改完成后,就可以用内联函数(115)消去设值函数。

class Person…

constructor(id) {
  this._id = id;
}
get name() {return this._name;}
set name(arg) {this._name = arg;}
get id() {return this._id;}
set id(arg) {this._id = arg;}