在代碼中,時常有就一類型碼(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() 根據員工不同的角色返回當月薪水,當然,此處邏輯僅為示意,勿深究。
用子類方法重構後的類結構是這樣的:
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 來取代條件表達式。
同樣,先上一張該方法重構後的類結構:
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