曾用名:以工厂函数取代构造函数(Replace Constructor with Factory Method)

leadEngineer = new Employee(document.leadEngineer, "E");
 
leadEngineer = createEngineer(document.leadEngineer);

动机

很多面向对象语言都有特别的构造函数,专门用于对象的初始化。需要新建一个对象时,客户端通常会调用构造函数。但与一般的函数相比,构造函数又常有一些丑陋的局限性。例如,Java 的构造函数只能返回当前所调用类的实例,也就是说,我无法根据环境或参数信息返回子类实例或代理对象;构造函数的名字是固定的,因此无法使用比默认名字更清晰的函数名;构造函数需要通过特殊的操作符来调用(在很多语言中是 new 关键字),所以在要求普通函数的场合就难以使用。

工厂函数就不受这些限制。工厂函数的实现内部可以调用构造函数,但也可以换成别的方式实现。

做法

新建一个工厂函数,让它调用现有的构造函数。

将调用构造函数的代码改为调用工厂函数。

每修改一处,就执行测试。

尽量缩小构造函数的可见范围。

范例

又是那个单调乏味的例子:员工薪资系统。我还是以 Employee 类表示“员工”。

class Employee…

constructor (name, typeCode) {
  this._name = name;
  this._typeCode = typeCode;
}
get name() {return this._name;}
get type() {
  return Employee.legalTypeCodes[this._typeCode];
}
static get legalTypeCodes() {
  return {"E": "Engineer", "M": "Manager", "S": "Salesman"};
}

使用它的代码有这样的:

调用方…

candidate = new Employee(document.name, document.empType);

也有这样的:

调用方…

const leadEngineer = new Employee(document.leadEngineer, "E");

重构的第一步是创建工厂函数,其中把对象创建的责任直接委派给构造函数。

顶层作用域…

function createEmployee(name, typeCode) {
  return new Employee(name, typeCode);
}

然后找到构造函数的调用者,并逐一修改它们,令其使用工厂函数。

第一处的修改很简单。

调用方…

candidate = createEmployee(document.name, document.empType);

第二处则可以这样使用工厂函数。

调用方…

const leadEngineer = createEmployee(document.leadEngineer, "E");

但我不喜欢这里的类型码——以字符串字面量的形式传入类型码,一般来说都是坏味道。所以我更愿意再新建一个工厂函数,把“员工类别”的信息嵌在函数名里体现。

调用方…

const leadEngineer = createEngineer(document.leadEngineer);

顶层作用域…

function createEngineer(name) {
  return new Employee(name, "E");
}