「復用是神話」已成程序員共識。傳統 OOP 以繼承為核心復用機制,卻因缺乏精確性導致:想復用單個方法卻被迫繼承整個類層次、父類變更波及所有子類、方法覆蓋關係難以追蹤、super 調用語義模糊。本文提出三步重構法,將功能遷移至全局可見的無狀態過程,結合接口多態與最小接口原則,構建高內聚、低耦合、真正可復用的代碼結構。
繼承為了復用一個方法,必須接受父類所有方法與字段。這種「全有或全無」機制引入不必要的耦合:
將執行單一概念任務的方法遷移至專門的工具類,僅依賴輸入參數與其他全局可見過程,零非局部變量依賴:
// 重構前:功能分散在實例方法中
class Polygon {
public int getPerimeter() { ... }
public boolean isConvex() { ... }
public boolean containsPoint(Point p) { ... }
}
// 重構後:委託給無狀態工具類
class Polygon {
public int getPerimeter() { return PolygonUtils.computePerimeter(this); }
public boolean isConvex() { return PolygonUtils.isConvex(this); }
public boolean containsPoint(Point p) { return PolygonUtils.containsPoint(this, p); }
}
// 無狀態工具類:命名慣例 p+原類名(p 表 process)
class PolygonUtils {
static public int computePerimeter(Polygon polygon) { ... }
static public boolean isConvex(Polygon polygon) { ... }
static public boolean containsPoint(Polygon polygon, Point p) { ... }
}
效果:客戶端無需繼承 Polygon 即可復用單個算法;Polygon 專注數據封裝與多態;工具類方法獨立可測、可組合。這與《設計模式》Strategy+Flyweight 組合異曲同工:Flyweight 確保無狀態單例,Strategy 封裝算法族。
Allen Holub 指出:「若方法所有參數皆為接口引用,則能操作編碼時尚不存在的類的對象。」將參數類型從具體類放寬為接口,劇擴大適用對象範圍:
// 重構前:綁定具體類 Rectangle
static boolean contains(Rectangle rect, int x, int y) { ... }
// 重構後:依賴接口 Rectangular
interface Rectangular { Rectangle getBounds(); }
static boolean contains(Rectangular rect, int x, int y) { ... }
集合場景的決定性優勢:處理 Collection<? extends Rectangular> 時,無需知悉具體類型,統一調用 getBounds()。若參數為 Rectangle,則需逐個 instanceof 轉型,違背開閉原則。
參數對象需實現的接口應「充分描述需求且贅餘最少」:
// 過度耦合:Window 包含大量無關方法
boolean areOverlapping(Window w1, Window w2)
// 最小接口:僅需矩形座標
interface Rectangular { Rectangle getBounds(); }
boolean areOverlapping(Rectangular r1, Rectangular r2)
若現有接口過大,定義新的單一職責接口(甚至僅供單一方法使用),如函數指針模式:
interface SortComparison { boolean comesBefore(Object a, Object b); }
static void sort(List list, SortComparison comp) { ... }
此接口專為 sort 服務,不污染全局命名空間,體現「接口隔離原則」。
三步結合形成新編程範式:
不適用場景:表示層代碼(UI 佈局、事件綁定)、強業務上下文綁定的領域邏輯、本質上不可復用的腳本代碼。這些代碼功能隨系統劇烈差異,強行復用反增複雜度。
本質上,這是將「繼承復用」轉向「組合復用」,將「類多態」轉向「接口多態」,將「狀態方法」轉向「無狀態函數」——更接近函數式編程思想,同時保留 Java 靜態類型優勢。