你手上有个条件式,它根据对象型别的不同而选择不同的行为。
将这个条件式的每个分支放进一个subclass 内的覆写函数中,然后将原始函数声明为抽象函数(abstract method)。
double getSpeed() {
switch (_type) {
case EUROPEAN:
return getBaseSpeed();
case AFRICAN:
return getBaseSpeed() - getLoadFactor() * _numberOfCoconuts;
case NORWEGIAN_BLUE:
return (_isNailed) ? 0 : getBaseSpeed(_voltage);
}
throw new RuntimeException ("Should be unreachable");
}
动机(Motivation)
在面向对象术语中,听上去最高贵的词非「多态」莫属。多态(polymorphism)最根本的好处就是:如果你需要根据对象的不同型别而采取不同的行为,多态使你不必编写明显的条件式(explicit conditional )。
正因为有了多态,所以你会发现:「针对type code(型别码)而写的switch 语句」 以及「针对type string (型别名称字符串)而写的if-then-else 语句」在面向对象程序中很少出现。
多态(polymorphism)能够给你带来很多好处。如果同一组条件式在程序许多地点出现,那么使用多态的收益是最大的。使用条件式时,如果你想添加一种新型别,就必须查找并更新所有条件式。但如果改用多态,只需建立一个新的subclass ,并在其中提供适当的函数就行了。class 用户不需要了解这个subclass ,这就大大降低了系统各部分之间的相依程度,使系统升级更加容易。
作法(Mechanics)
使用Replace Conditional with Polymorphism之前,你首先必须有一个继承结构。你可能已经通过先前的重构得到了这一结构。如果还没有,现在就需要建立它。
要建立继承结构,你有两种选择: Replace Type Code with Subclasses 和 Replace Type Code with State/Strategy。前一种作法比较简单,因此你应该尽可能使用它。但如果你需要在对象创建好之后修改type code;就不能使用subclassing 作法,只能使用State/Strategy 模式。此,如果由于其他原因你要重构的class 已经有了subclass ,那么也得使用State/Strategy 。记住,如果若干switch 语句针对的是同一个type code;你只需针对这个type code 建立一个继承结构就行 了。
现在,可以向条件式开战了。你的目标可能是switch(case)语句,也可能是if 语句。
· | 如果要处理的条件式是一个更大函数中的一部分,首先对条件式进行分析,然后使用Extract Method 将它提炼到一个独立函数去。 |
· | 如果有必要,使用Move Method 将条件式放置到继承结构的顶端。 |
· | 任选一个subclass ,在其中建立一个函数,使之覆写superclass 中容纳条件式的那个函数。将「与subclass 相关的条件式分支」拷贝到新建函数中,并对它进行适当调整。 |
Ø | 为了顺利进行这一步骤,你可能需要将superclass 中的某些private 值域声明为protected 。 |
· | 编译,测试。 |
· | 在superclass 中删掉条件式内被拷贝出去的分支。 |
· | 编译,测试。 |
· | 针对条件式的每个分支,重复上述过程,直到所有分支都被移到subclass 内的函数为止。 |
· | 将superclass 之中容纳条件式的函数声明为抽象函数(abstract method)。 |
范例:(Example)
请允许我继续使用「员工与薪资」这个简单而又乏味的例子。我的classes是从Replace Type Code with State/Strategy 那个例子中拿来的,因此示意图就如图9.1所示(如果想知道这个图是怎么得到的,请看第8章范例)。
图9.1 继承机构
class Employee...
int payAmount() {
switch (getType()) {
case EmployeeType.ENGINEER:
return _monthlySalary;
case EmployeeType.SALESMAN:
return _monthlySalary + _commission;
case EmployeeType.MANAGER:
return _monthlySalary + _bonus;
default:
throw new RuntimeException("Incorrect Employee");
}
}
int getType() {
return _type.getTypeCode();
}
private EmployeeType _type;
abstract class EmployeeType...
abstract int getTypeCode();
class Engineer extends EmployeeType...
int getTypeCode() {
return Employee.ENGINEER;
}
... and other subclasses
switch 语句已经被很好地提炼出来,因此我不必费劲再做一遍。不过我需要将它移至EmployeeType class,因为EmployeeType 才是被subclassing 的class 。
class EmployeeType...
int payAmount(Employee emp) {
switch (getTypeCode()) {
case ENGINEER:
return emp.getMonthlySalary();
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
case MANAGER:
return emp.getMonthlySalary() + emp.getBonus();
default:
throw new RuntimeException("Incorrect Employee");
}
}
由于我需要EmployeeType class 的数据,所以我需要将Employee 对象作为参数传递给payAmount()。这些数据中的一部分也许可以移到EmployeeType class 来,但那是另一项重构需要关心的问题了。
调整代码,使之通过编译,然后我修改Employee 中的payAmount() 函数,令它委托(delegate,转调用)EmployeeType :
class Employee...
int payAmount() {
return _type.payAmount(this);
}
现在,我可以处理switch 语句了。这个过程有点像淘气小男孩折磨一只昆虫——每次掰掉它一条腿 6。首先我把switch 语句中的"Engineer"这一分支拷贝到Engineer class:
class Engineer...
int payAmount(Employee emp) {
return emp.getMonthlySalary();
}
6译注:「腿」和条件式「分支」的英文都是"leg"。作者幽默地说「掰掉一条腿」, 意思就是「去掉一个分支」。
这个新函数覆写了superclass 中的switch 语句之内那个专门处理"Engineer"的分支。我是个徧执狂,有时我会故意在case 子句中放一个陷阱,检查Engineer class 是否正常工作(是否被调用):
class EmployeeType...
int payAmount(Employee emp) {
switch (getTypeCode()) {
case ENGINEER:
throw new RuntimeException ("Should be being overridden");
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
case MANAGER:
return emp.getMonthlySalary() + emp.getBonus();
default:
throw new RuntimeException("Incorrect Employee");
}
}
接下来,我重复上述过程,直到所有分支都被去除为止:
class Salesman...
int payAmount(Employee emp) {
return emp.getMonthlySalary() + emp.getCommission();
}
class Manager...
int payAmount(Employee emp) {
return emp.getMonthlySalary() + emp.getBonus();
}
然后,将superclass 的payAmount() 函数声明为抽象函数:
class EmployeeType...
abstract int payAmount(Employee emp);