歷史系統常因語言限制而大量濫用 if-else,導致程式碼難以維護。本文以一個會議管理系統的 SystemManager 為例,示範如何透過重構將其轉化為物件導向設計,並引入測試保護。
public class SystemManager {
public static final int LOGGEDIN = 0;
public static final int LOGGEDOUT = 1;
public static final int IDLE = 2;
int state;
public void updateState(int state) {
if (state == LOGGEDIN) { /* ... */ }
else if (state == LOGGEDOUT) { /* ... */ }
else if (state == IDLE) { /* ... */ }
else { throw new IllegalArgumentException("unknown state"); }
this.state = state;
}
}
隨狀態增加,if-else 將持續膨脹,違反開放封閉原則。
使用 JUnit 編寫 SystemManagerTest,驗證 login()、logout()、idle() 狀態轉換。測試通過後方可放心重構。
public enum SystemState { LOGGEDIN, LOGGEDOUT, IDLE }
將 state 欄位型別改為 SystemState,更新方法簽名與測試碼。此步驟提升型別安全,但 updateState 內的條件判斷仍存在。
核心洞見:updateState 需要針對不同 Enum 執行不同邏輯,因 Enum 本身無行為。解決方案是建立抽象類別 SystemStatePerformer,每個子類別負責一種狀態的具體行為。
public abstract class SystemStatePerformer {
private final SystemState state;
private Image image;
public SystemStatePerformer(SystemState state, Image image) { ... }
public abstract void perform();
}
避免為每個狀態建立獨立類別類,採用 Factory 方法回傳匿名內部類:
public class SystemStatePerformerFactory {
private static SystemStatePerformerFactory INSTANCE = new SystemStatePerformerFactory();
public static SystemStatePerformer getSystemStatePerformer(SystemState state) {
switch (state) {
case LOGGEDIN: return createLoggedInPerformer();
case IDLE: return createIdlePerformer();
case LOGGEDOUT: return createLoggedOutPerformer();
default: throw new IllegalAccessError("Unknown status");
}
}
private static SystemStatePerformer createLoggedInPerformer() {
return new SystemStatePerformer(LOGGEDIN, getImage("loggedin.gif")) {
@Override public void perform() { /* 登入後邏輯 */ }
};
}
// ... 其他 createXxxPerformer 方法
}
public class SystemManager {
SystemStatePerformer statePerformer;
public void updateState(SystemState state) {
this.statePerformer = SystemStatePerformerFactory.getInstance().getSystemStatePerformer(state);
statePerformer.perform(); // 多態調用,if-else 消失
}
}
同步更新測試碼,驗證 statePerformer.getState()。
Factory 內仍有 switch,本質同 if-else。改進:啟動時透過反射掃描標註 @FactoryMethod 的方法,建立所有 Performer 實例並存入 Map<SystemState, SystemStatePerformer>,查詢時直接 map.get(state),實現 O(1) 查找並完全移除條件分支。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FactoryMethod {}
此重構對應《Refactoring to Patterns》第七章「Replace Conditional Dispatcher with Command」,但有兩點創新:
重構後代碼符合里氏替換原則 (LSP),子類別可完全替換基類別,系統更具擴展性與可維護性。