重构:越来越长的 switch ... case 和 if ... else if ... else
在代码中,时常有就一类型码(Type Code)而展开的如 switch ... case 或 if ... else if ... else 的条件表达式。随着项目业务逻辑的增加及代码经年累月的修改,这些条件判断逻辑往往变得越来越冗长。特别是当同样的逻辑判断出现在多个地方的时候(结构示意如下),代码的可读性和维护难易程度将变得非常的糟糕。每次修改时,你必须找到所有有逻辑分支的地方,并修改它们。
1 switch(type) 2 { 3 case "1": 4 ... 5 break; 6 case "2": 7 ... 8 break; 9 case default: 10 ... 11 break; 12 } 13 14 ... ... 15 ... ... 16 17 switch(type) 18 { 19 case "1": 20 ... 21 break; 22 case "2": 23 ... 24 break; 25 case default: 26 ... 27 break; 28 }
当代码中出现这样情况的时候,你就应考虑该重构它了(又有一种说法:要极力的重构 switch ... case 语句,避免它在你的代码中出现)。
重构掉类型码(Type Code)判断的条件表达式,我们需要引入面向对象的多态机制。第一种方式:使用子类来替换条件表达式。
我们以计算不同 role 的员工薪水的 Employee 类为例,一步步讲解重构的过程。原始待重构的类结构如下:
1 public class Employee_Ori 2 { 3 private int _type; 4 5 private const int ENGINEER = 1; 6 private const int SALESMAN = 2; 7 private const int MANAGER = 3; 8 9 private decimal _baseSalary = 10000; 10 private decimal _royalty = 100; 11 private decimal _bonus = 400; 12 13 public Employee_Ori(int type) 14 { 15 this._type = type; 16 } 17 18 public decimal getEmployeeSalary() 19 { 20 var monthlySalary = 0m; 21 22 switch (this._type) 23 { 24 case ENGINEER: 25 monthlySalary = _baseSalary; 26 break; 27 case SALESMAN: 28 monthlySalary = _baseSalary + _royalty; 29 break; 30 case MANAGER: 31 monthlySalary = _baseSalary + _bonus; 32 break; 33 } 34 35 return monthlySalary; 36 } 37 }
其中方法 getEmployeeSalary() 根据员工不同的角色返回当月薪水,当然,此处逻辑仅为示意,勿深究。
用子类方法重构后的类结构是这样的:
下面详细展开。首先,以类型码(Type Code)的宿主类 Employee 类为基类,为每个角色建立子类。
1 public class Engineer_Sub : Employee_Sub 2 { 3 ... 4 } 5 6 public class Salesman_Sub : Employee_Sub 7 { 8 ... 9 } 10 11 public class Manager_Sub : Employee_Sub 12 { 13 ... 14 }
同时,将 Employee 基类的构造函数改造为静态 Create 工厂函数。
1 public static Employee_Sub Create(int type) 2 { 3 Employee_Sub employee = null; 4 5 switch (type) 6 { 7 case ENGINEER: 8 employee = new Engineer_Sub(); 9 break; 10 case SALESMAN: 11 employee = new Salesman_Sub(); 12 break; 13 case MANAGER: 14 employee = new Manager_Sub(); 15 break; 16 } 17 18 return employee; 19 }
该静态工厂方法中也含有一 switch 逻辑。而重构后的代码中 switch 判断只有这一处,且只在对象创建的过程中涉及,不牵扯任何的业务逻辑,所以是可以接受的。
每个角色子类均覆写基类中的 getEmployeeSalary() 方法,应用自己的规则来计算薪水。
1 private decimal _baseSalary = 10000; 2 3 public override decimal getEmployeeSalary() 4 { 5 return _baseSalary; 6 }
这样,基类 Employee 中的 getEmployeeSalary() 方法已无实际意义,将其变为 abstract 方法。
1 public abstract decimal getEmployeeSalary();
子类已经完全取代了臃肿的 switch 表达式。如果 Engineer, Salesman 或者 Manager 有新的行为,可在各自的子类中添加方法。或后续有新的 Employee Type, 完全可以通过添加新的子类来实现。在这里,通过多态实现了服务与其使用这的分离。
但是,在一些情况下,如对象类型在生命周期中需要变化(细化到本例,如 Engineer 晋升为 Manager)或者类型宿主类已经有子类,则使用子类重构的办法就无法奏效了。这时应用第二种方法:用 State/Strategy 来取代条件表达式。
同样,先上一张该方法重构后的类结构:
抽出一 EmployeeType 类,类型码的宿主类 Employee 对其进行引用。
1 public class Employee_State 2 { 3 // employee's type can be changed 4 private EmployeeType_State _employeeType = null; 5 }
EmployeeType 为基类,具体员工类型作为其子类。EmployeeType 类中含有一创建员工类型的静态工厂方法 Create。
1 public static EmployeeType_State Create(int type) 2 { 3 EmployeeType_State empType = null; 4 5 switch (type) 6 { 7 case ENGINEER: 8 empType = new EngineerType_State(); 9 break; 10 case SALESMAN: 11 empType = new SalesmanType_State(); 12 break; 13 case MANAGER: 14 empType = new ManagerType_State(); 15 break; 16 } 17 18 return empType; 19 }
将员工薪水的计算方法 getEmployeeSalary() 从 Employee 类迁徙到员工类型基类 EmployeeType 中。各具体员工类型类均 override 该方法。考虑到 EmployeeType 已无具体业务逻辑意义,将 EmployeeType 中的 getEmployeeSalary() 方法改为抽象方法。
1 public class EngineerType_State : EmployeeType_State 2 { 3 private decimal _baseSalary = 10000; 4 5 public override decimal getEmployeeSalary() 6 { 7 return _baseSalary; 8 } 9 }
最后,附上示意代码,点这里下载。
参考资料:
《重构 改善既有代码的设计》 Martin Fowler