本章核心#
上一章結尾留下的 updateTile 仍違反多條規則。本章的主題是合併相似的程式碼——無論是相似的 class、相似的條件判斷、還是跨 class 的重複邏輯,都有對應的重構模式可以處理。同時引入條件運算的算術規則 (condition arithmetic)、UML 類別圖基礎,以及最精巧的 INTRODUCE STRATEGY PATTERN。
Refactoring Pattern: UNIFY SIMILAR CLASSES#
問題#
Stone 與 FallingStone 幾乎完全相同,只差在 isFallingStone() 的回傳值和 moveHorizontal() 的行為。這種「只差一組常數方法」的情況,可以合併為單一 class。
做法(類似分數加法)#
第一階段:讓非基底方法相同
- 在每個 class 的
moveHorizontal主體外加上if (true) { } - 把
true替換為基底方法的比較式(如this.isFallingStone() === false) - 將另一個 class 的主體以
else貼入,使兩邊的moveHorizontal完全一致
第二階段:合併 class
- 引入
falling欄位,在 constructor 中賦予各自的常數值 - 讓
isFallingStone()改為回傳this.falling - 逐步將常數改為 constructor 參數
- 刪除其中一個 class,把所有實例化導向保留的 class
合併後的 Stone 接受 falling 參數,隱藏的 type code 被暴露:布林值 falling 就是一個 type code。
進一步處理暴露出的 type code#
- 把
boolean改為 enumFallingState { FALLING, RESTING } - 再用 REPLACE TYPE CODE WITH CLASSES 把 enum 轉為 interface +
Falling/Restingclass - 用 PUSH CODE INTO CLASSES 把
moveHorizontal中的邏輯推入Falling和Resting - 用 TRY DELETE THEN COMPILE 清除不再使用的
isResting()等方法
同樣的流程也適用於 Box / FallingBox,而且可以重用 FallingState interface。
flowchart TD
subgraph phase1["第一階段:讓方法相同"]
A["各 class 方法外\n加上 if(true)"] --> B["替換為基底方法\n比較式"]
B --> C["將另一 class 主體\n以 else 貼入"]
end
subgraph phase2["第二階段:合併 class"]
D["引入欄位\nconstructor 賦常數值"] --> E["基底方法改為\n回傳欄位"]
E --> F["常數改為\nconstructor 參數"]
F --> G["刪除其中一個 class"]
end
phase1 --> phase2
subgraph phase3["進一步處理 type code"]
H["boolean → enum"] --> I["REPLACE TYPE CODE\nWITH CLASSES"]
I --> J["PUSH CODE\nINTO CLASSES"]
J --> K["TRY DELETE\nTHEN COMPILE"]
end
phase2 -->|"暴露隱藏的\ntype code"| phase3Refactoring Pattern: COMBINE IFS#
問題#
兩個相鄰的 else if 有完全相同的主體。
做法#
- 確認兩個主體完全一致
- 刪除第一個
if的右括號到第二個else if的左括號之間的內容,插入|| - 在整個複合條件外加上括號以確保語義不變
- 移除多餘的括號
在 updateTile 上的應用#
引入 rest() 方法後,最後兩個 else if(isFallingStone 與 isFallingBox)的主體相同,合併為 isFallingStone() || isFallingBox(),再推入 class 成為 isFalling()。
條件運算的算術規則 (Condition Arithmetic)#
|| 的行為類似加法 +,&& 的行為類似乘法 x。

Figure 5.1: Mnemonic to help remember precedence
這讓我們可以像操作數學算式一樣簡化條件式。例如:
A && C || B && C → (A || B) && C
Figure 5.2: Arithmetic rules

Figure 5.3: Applying arithmetic rules
透過這種轉換,updateTile 中的 map[y][x].isStony() && air || map[y][x].isBoxy() && air 被簡化為 (isStony() || isBoxy()) && air,再推入 class 成為 canFall()。
上述算術規則僅在條件無副作用時才成立。這就是 USE PURE CONDITIONS 規則存在的原因。
Rule: USE PURE CONDITIONS#
- 條件(
if、while後面的表達式)必須是純粹的 (pure)——不可賦值、不可拋例外、不可做 I/O - 有副作用的條件會阻礙算術規則的套用,也違反直覺(我們不預期條件會改變狀態)
- 若無法控制實作(如
reader.readLine()同時移動指標),可用 Cache 將副作用與回傳值分離
UML 類別圖入門#
類別圖用來描繪 class 與 interface 之間的結構關係,在說明 Design Pattern 時特別常用。

Figure 5.4: Class diagram

Figure 5.5: UML relations
常用的兩種關係#
| 關係 | 符號 | 語義 |
|---|---|---|
| Composition | 實心菱形箭頭 | 「A has a B」——A 持有 B 的實例作為欄位 |
| Implementation | 空心三角箭頭 + 虛線 | 「B implements A」——B 實作了 A interface |
由於 ONLY INHERIT FROM INTERFACES 規則,繼承箭頭(實心三角)在本書中幾乎不會出現。

Figure 5.6: Implementation

Figure 5.7: Composition
Refactoring Pattern: INTRODUCE STRATEGY PATTERN#
問題#
Stone 和 Box 中的 update 邏輯完全相同——掉落行為應該保持同步,且未來可能還有更多 Tile 需要此行為。這裡我們不希望 divergence(如第四章對 draw 的態度),而是希望 convergence。
做法#
- 用 EXTRACT METHOD 隔離要統一的邏輯
- 建立新 class(如
FallStrategy) - 在原 class 的 constructor 中實例化新 class
- 把方法移入新 class
- 若依賴原 class 的欄位,一併搬移欄位並建立 accessor
- 用參數取代新 class 中對
this的引用 - 用 INLINE METHOD 反轉第一步的抽取
flowchart TD
A["EXTRACT METHOD\n隔離要統一的邏輯"] --> B["建立新 class\n如 FallStrategy"]
B --> C["在原 class constructor\n中實例化新 class"]
C --> D["將方法移入新 class"]
D --> E{"依賴原 class 欄位?"}
E -->|是| F["搬移欄位\n建立 accessor"]
E -->|否| G["用參數取代\nnew class 中的 this"]
F --> G
G --> H["INLINE METHOD\n反轉第一步的抽取"]
Figure 5.8: Class diagram with a focus on FallStrategy

Figure 5.9: Strategy pattern as a class diagram
Strategy Pattern 的意義#
- 物件取代了
if,是 late binding 的終極形式 - 在執行時期,甚至可以載入編譯時未知的 class,無縫整合到控制流中
- 即便當下不需要變體,引入 Strategy 也為未來保留了擴展點
如果需要引入 variance,再透過 EXTRACT INTERFACE FROM IMPLEMENTATION 加上 interface。不需要的話就不加——遵循「不要有只有一個實作的 interface」規則。
Rule: NO INTERFACE WITH ONLY ONE IMPLEMENTATION#
- 只有一個實作的 interface 不增加可讀性,反而發出虛假的「這裡有變體」訊號
- 它徒增檔案數量與心智負擔
- 等到真正需要 variance 時再用 EXTRACT INTERFACE FROM IMPLEMENTATION 建立 interface 即可
Refactoring Pattern: EXTRACT INTERFACE FROM IMPLEMENTATION#
步驟#
- 建立一個與 class 同名的新 interface
- 將原 class 重新命名,並讓它
implements新 interface - 編譯,處理所有錯誤:
new語句改用新 class 名稱- 其餘報錯的方法加入 interface
應用#
以 removeLock1 / removeLock2 為例:先用 INTRODUCE STRATEGY PATTERN 將 isLock1() 的檢查抽成 RemoveStrategy class,再用 EXTRACT INTERFACE FROM IMPLEMENTATION 建立 RemoveStrategy interface 與 RemoveLock1 / RemoveLock2 兩個實作,最終合併為單一 remove(shouldRemove: RemoveStrategy) 函式。
同樣的技巧也用來合併 Key1/Key2 與 Lock1/Lock2,引入 KeyConfiguration class 來統一管理顏色、鎖 ID、移除策略之間的關聯。
classDiagram
class RemoveStrategy {
<<interface>>
+check(tile) bool
}
class RemoveLock1 {
+check(tile) bool
}
class RemoveLock2 {
+check(tile) bool
}
class KeyConfiguration {
-color
-lockId
-removeStrategy: RemoveStrategy
+removeLock()
+setColor(g)
}
RemoveStrategy <|.. RemoveLock1
RemoveStrategy <|.. RemoveLock2
KeyConfiguration --> RemoveStrategy本章小結#
- UNIFY SIMILAR CLASSES 合併只差常數方法的 class,暴露隱藏的 type code 以便進一步處理
- COMBINE IFS 合併主體相同的相鄰
if,暴露||關係以便推入 class - 條件算術規則讓我們像簡化數學式一樣簡化條件式,前提是遵守 USE PURE CONDITIONS
- UML 類別圖幫助我們以圖形方式理解 class 關係
- INTRODUCE STRATEGY PATTERN 是本書最精巧的重構模式,用獨立的 class 統一跨 class 的重複邏輯
- NO INTERFACE WITH ONLY ONE IMPLEMENTATION 避免不必要的抽象;需要時再用 EXTRACT INTERFACE FROM IMPLEMENTATION 建立 interface