《Design Patterns》開宗明義:「Favor composition over inheritance」。類別繼承雖直觀,卻帶來強耦合、脆弱基底類別、方法爆炸等顽疾。本文提出三個漸進式重構策略,將「繼承式復用」轉化為「組合式復用」,實現更精準、靈活的代碼共享。
想復用 Polygon 的 getPerimeter(),卻被迫繼承整個類別,連帶引入無關的 isConvex()、containsPoint() 及所有欄位。
class Polygon {
public int getPerimeter() { ... }
public boolean isConvex() { ... }
public boolean containsPoint(Point p) { ... }
}
pPolygonclass Polygon {
public int getPerimeter() { return pPolygon.computePerimeter(this); }
public boolean isConvex() { return pPolygon.isConvex(this); }
public boolean containsPoint(Point p) { return pPolygon.containsPoint(this, p); }
}
class pPolygon { // 小寫開頭標記:純函數容器,非領域實體
static int computePerimeter(Polygon polygon) { ... }
static boolean isConvex(Polygon polygon) { ... }
static boolean containsPoint(Polygon polygon, Point p) { ... }
}
pPolygon.computePerimeter(poly),無需繼承 Polygon。p 前綴(procedure/process)區分「函數容器」與「領域實體」,語義清晰。「我們針對介面編程而非實現編程,才能真正復用方法。若方法參數皆為介面,它就能操作編寫代碼時尚不存在的類別。」—— Allen Holub
// 之前:綁定具體類別
static boolean contains(Rectangle rect, int x, int y) { ... }
// 之後:依賴介面
interface Rectangular { Rectangle getBounds(); }
static boolean contains(Rectangular rect, int x, int y) {
Rectangle r = rect.getBounds();
// 邏輯不變,但現在接受所有實現 Rectangular 的物件
}
// 檢查集合中任意矩形是否重疊
static boolean areAnyOverlapping(Collection<? extends Rectangular> rects) {
// 無法轉型為 Rectangular 介面,就無法統一訪問 getBounds()
// 若參數為 Collection,則排除 RoundRect、ImageView 等潛在類別
}
介面參數將方法的適用域從「單一類別」擴展至「實現契約的所有類別」,這正是開放封閉原則(OCP)的具體體現。
參數介面應 恰好滿足方法需求,不多不少。介面越小,實現類越多,復用面越廣。
// 反例:Window 傳入過多無關能力
static boolean areOverlapping(Window w1, Window w2) { ... }
// 正例:僅需矩形邊界
static boolean areOverlapping(Rectangular r1, Rectangular r2) { ... }
interface SortComparison {
boolean comesBefore(Object a, Object b);
}
static void sort(List list, SortComparison comp) {
// 僅需比較邏輯,SortComparison 成為「策略鉤子」
}
此時介面不再是領域契約,而是 行為插槽,類比 C 函數指標、Java 8 Comparator、Kotlin 函數類型。
| 層級 | 職責 | 典型形式 |
|---|---|---|
| 領域實體 | 封裝狀態與不變約束 | class Order { ... } |
| 行為工具 | 無狀態演算法、計算、驗證 | class pOrder { static BigDecimal calcTotal(Order o) } |
| 策略介面 | 定義可插拔行為契約 | interface PricingStrategy { Money apply(Order o); } |
| 協調服務 | 編排實體、工具、策略 | class OrderService { ... } |
Order.place() 涉及狀態機轉換,不宜剝離為靜態函數。三大策略層層遞進:函數抽離 → 介面參數化 → 最小介面,將代碼從「繼承樹」解放為「函數森林 + 介面網絡」。這不是否定 OOP,而是回歸 OOP 精髓——封裝變化點,釐清依賴方向。下次想寫 extends BaseUtil 時,請先問自己:「能不能改成 pUtil.staticMethod(this)?」