重构函数-Replace Constructor with Factory Method以工厂函数取代构造函数十二
1.以工厂函数取代构造函数
1.1.使用场景
你希望在创建对象时不仅仅是做简单的建构动作。将构造函数替换为工厂函数。
使用Replace Constructor with Factory Method (304)的最显而易见的动机,就是在派生子类的过程中以工厂函数取代类型码。你可能常常需要根据类型码创建相应的对象,现在,创建名单中还得加上子类,那些子类也是根据类型码来创建。然而由于构造函数只能返回单一类型的对象,因此你需要将构造函数替换为工厂函数[Gang of Four]。
此外,如果构造函数的功能不能满足你的需要,也可以使用工厂函数来代替它。工厂函数也是Change Value to Reference (179)的基础。你也可以令你的工厂函数根据参数的个数和类型,选择不同的创建行为。
1.2.如何做
- 新建一个工厂函数,让它调用现有的构造函数。
- 将调用构造函数的代码改为调用工厂函数。
- 每次替换后,编译并测试。
- 将构造函数声明为private。
- 编译。
1.3.示例
class Employee {
private int _type;
static final int ENGINEER = 0;
static final int SALESMAN = 1;
static final int MANAGER = 2;
Employee (int type) {
_type = type;
}
我希望为Employee提供不同的子类,并分别给予它们相应的类型码。因此,我需要建立一个工厂函数
static Employee create(int type) {
return new Employee(type);
}
然后,我要修改构造函数的所有调用点,让它们改用上述新建的工厂函数,并将构造函数声明为private
client code...
Employee eng = Employee.create(Employee.ENGINEER);
class Employee...
private Employee (int type) {
_type = type;
}
根据字符串创建子类对象
迄今为止,我还没有获得什么实质收获。目前的好处在于:我把“对象创建请求的接收者”和“被创建对象所属的类”分开了。如果我随后使用Replace Type Code with Subclasses (223)把类型码转换为Employee的子类,就可以运用工厂函数,将这些子类对用户隐藏起来
static Employee create(int type) {
switch (type) {
case ENGINEER:
return new Engineer();
case SALESMAN:
return new Salesman();
case MANAGER:
return new Manager();
default:
throw new IllegalArgumentException("Incorrect type code value");
}
}
可惜的是,这里面有一个switch语句。如果我添加一个新的子类,就必须记得更新这里的switch语句,而我又偏偏很健忘。
绕过这个switch语句的一个好办法是使用Class.forName()。
第一件要做的事是修改参数类型,这从根本上说是Rename Method (273)的一种变体。
首先我得建立一个函数,让它接收一个字符串参数:
static Employee create (String name) {
try {
return (Employee) Class.forName(name).newInstance();
} catch (Exception e) {
throw new IllegalArgumentException ("Unable to instantiate" + name);
}
}
然后让稍早那个“create()函数int版”调用新建的“create()函数String版
class Employee {
static Employee create(int type) {
switch (type) {
case ENGINEER:
return create("Engineer");
case SALESMAN:
return create("Salesman");
case MANAGER:
return create("Manager");
default:
throw new IllegalArgumentException("Incorrect type code value");
}
}
然后,我得修改create() 函数的调用者,将下列这样的语句
Employee.create(ENGINEER)
修改为
Employee.create("Engineer")
完成之后,我就可以将“create()函数int版”移除了。
现在,当我需要添加新的Employee子类时,就不再需要更新create()函数了。但我却因此失去了编译期检验,使得一个小小的拼写错误就可能造成运行期错误。如果有必要防止运行期错误,我会使用明确函数来创建对象(见本页下节)。但这样一来,每添加一个新的子类,我就必须添加一个新函数。这就是为了类型安全而牺牲掉的灵活性。还好,即使我做了错误选择,也可以使用Parameterize Method (283)或Replace Parameter with Explicit Methods (285)撤销决定。
另一个必须谨慎使用Class.forName()的原因是:它向用户暴露了子类名称。不过这并不太糟糕,因为你可以使用其他字符串,并在工厂函数中执行其他行为。这也是不使用Inline Method (117)去除工厂函数的一个好理由。
以明确函数创建子类
我可以通过另一条途径来隐藏子类——使用明确函数。如果你只有少数几个子类,而且它们都不再变化,这条途径是很有用的。我可能有个抽象的Person类,它有两个子类:Male和Female。首先我在超类中为每个子类定义一个工厂函数:
class Person...
static Person createMale(){
return new Male();
}
static Person createFemale() {
return new Female();
}
然后我可以把下面的调用
Person kent = new Male();
替换成
Person kent = Person.createMale();
但是这就使得超类必须知晓子类。如果想避免这种情况,你需要一个更为复杂的设计,例如Product Trader模式[Bäumer and Riehle]。绝大多数情况下你并不需要如此复杂的设计,上面介绍的做法已经绰绰有余。