Summary: 你手上有个条件表达式,它根据对象类型的不同而选择不同的行为。将这个条件表达式的每个分支放进一个子类内的覆写函数中,然后将原始函数声明为抽象函数。
Motivation: 在面向对象术语中,听上去最高贵的词非“多态”莫属。多态最根本的好处就是:如果你需要根据对象的不同类型而采取不同的行为,多态使你不必编写明显的条件表达式。
正因为有了多态,所以你会发现:“类型码的switch语句”以及“基于类型名称的if-then-else语句”在面向对象程序中很少出现。
多态能够给你带来很多好处。如果同一组条件表达式在程序许多地点出现,那么使用多态的收益是最大的。使用条件表达式时,如果你想添加一种新类型,就必须查找并更新所有条件表达式。但如果改用多态,只需建立一个新的子类,并在其中提供适当的函数就行了。类的用户不需要了解这个子类,这就大大降低了系统各部分之间的依赖,使系统升级更加容易。
Mechanics:
使用Replace Conditional with Polymorphism之前,首先必须由一个继承结构。你可能已经通过先前的重构得到了这一结构。如果还没有,现在就需要建立它。
要建立继承结构,有两种选择:Replace Type Code with Subclasses和Replace Type Code with State/Strategy。前一种做法比较简单,因此应该尽可能使用它。但如果你需要在对象创建好了之后修改类型码,就不能使用继承手法,只能使用State/Strategy模式。此外,如果由于其他原因,要重构的类已经有了子类,那么也得使用State/Strategy。记住,如果若干switch语句针对的是同一个类型码,你只需针对这个类型码建立一个继承结构就行了。
现在,可以向条件表达式开战了。你的目标可能是switch语句,也可能是if语句。
1.如果要处理的条件表达式是一个更大函数中的一部分,首先对条件表达式进行分析,然后使用Extract Method将它提炼到一个独立函数去。
2.如果有必要,使用Move Method将条件表达式放置到继承结构的顶端。
3.任选一个子类,在其中建立一个函数,使之覆写超类中容纳条件表达式的那个函数。将与该子类相关的条件表达式分支复制到新建函数中,并对它进行适当调整。
为了顺利进行这一步骤,你可能需要将超类中的某些private字段声明为protected。
4.编译,测试。
5.在超类中删掉条件表达式内被复制了的分支。
6.编译,测试。
7.针对条件表达式的每个分支,重复上述过程,直到所有分支都被移到子类内的函数为止。
8.将超类之中容纳条件表达式的函数声明为抽象函数。
范例:
请允许我继续使用“员工与薪资”这个简单而又乏味的例子。我们的类是从Replace Type Code with State/Strategy那个例子中拿来的,因此示意图如下所示:
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类,因为EmployeeType才是要被继承的类。
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();
defaule:
throw new RuntimeException("Incorrect Employee");
}
}
由于我们需要Employee的数据,所以需要将Employee对象作为参数传递给payAmount()。这些数据中的一部分也许可以移到EmployeeType来,但那时另一项重构需要关心的问题了。
调整代码,使之通过编译,然后我们修改Employee中的payAmount()函数,令它委托EmployeeType:
class Employee...
int payAmount(){
return _type.payAmount(this);
}
现在,我们可以处理switch语句了。这个过程有点象淘气小男孩折磨一只昆虫--每次掰掉它一条腿。首先我们把switch语句中的Engineer这一分支复制到Engineer类:
class Engineer...
int payAmount(Employee emp){
return emp.getMonthlySalary();
}
这个新函数覆写了超类中的switch语句内专门处理Engineer的分支。我们可以故意在case子句中放一个陷阱,检查Engineer子类是否正常工作:
class EmployeeType...
int payAmount(Employee emp){
switch(getTypeCode()){
case EmployeeType.ENGINEER:
throw new RuntimeException("should be being overriden");
case SALESMAN:
return emp.getMonthlySalary() + emp.getCommission();
case MANAGER:
return emp.getMonthlySalary() + emp.getBonus();
defaule:
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();
}
然后将超类的payAmount()函数声明为抽象函数:
class EmployeeType...
abstract int payAmount(Employee emp);
来源:oschina
链接:https://my.oschina.net/u/134516/blog/213786