zoukankan      html  css  js  c++  java
  • 重构:越来越长的 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

  • 相关阅读:
    初等数论及其应用——Lucas定理
    数据结构编程实验——chapter10-应用经典二叉树编程
    Coursera课程 Programming Languages 总结
    Coursera课程 Programming Languages, Part C 总结
    读《如何阅读一本书》有感
    Educational Codeforces Round 34
    Coursera课程 Programming Languages, Part B 总结
    Codeforces #451 Div2 F
    Codeforces #452 Div2 F
    Coursera课程 Programming Languages, Part A 总结
  • 原文地址:https://www.cnblogs.com/isun/p/5003685.html
Copyright © 2011-2022 走看看