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);