代碼復用不是複製粘貼,而是識別變化維度,將其封裝為可替換的抽象。本文總結三大核心原則,並通過「文檔多格式輸出」案例對比繼承與組合架構。
interface 或 abstract class,客戶代碼僅依賴抽象類型。List<T> vs ArrayList、DataSource vs DruidDataSource、Mapper 接口 vs MyBatis 動態代理實現。| 維度 | 類繼承 (白箱復用) | 對象組合 (黑箱復用) |
|---|---|---|
| 綁定時機 | 編譯期靜態 | 運行期動態 |
| 封裝性 | 破壞 (子類可見父類 protected/private) | 保持 (僅通過接口交互) |
| 耦合度 | 高 (父類變更波及所有子類) | 低 (接口穩定,實現可獨立演進) |
| 靈活性 | 低 (無法運行時切換父類實現) | 高 (可運行時替換組合對象) |
| 類爆炸風險 | 高 (多維度變化導致笛卡爾積子類) | 低 (維度正交,組合數線性增長) |
誤區澄清:非完全否定繼承。「是一種」關係 (Liskov 替換原則成立) 用繼承;「有一個」關係 用組合。實際多為混合:抽象骨架用繼承 (AbstractTemplate),變化策略用組合 (Strategy)。
abstract class AbstractExampleDocument {
public void output(Example structure) {
if (structure != null) this.format(structure);
}
protected abstract void format(Example structure);
}
// 維度 1: 格式 (Text, HTML, PDF, XML...)
// 維度 2: 結構類型 (Report, Invoice, Letter...)
// 維度 3: 壓縮 (無, GZIP, ZIP...)
// 繼承方案需創建 4×3×2 = 24 個具體子類!新增任一維度指數級增長。
// 1. 核心抽象:文檔導出器
interface DocumentExporter {
void export(Document doc, OutputStream out);
}
// 2. 格式策略 (Strategy)
class TextExporter implements DocumentExporter { ... }
class HtmlExporter implements DocumentExporter { ... }
class PdfExporter implements DocumentExporter { ... }
// 3. 壓縮裝飾器 (Decorator)
class CompressingExporter implements DocumentExporter {
private DocumentExporter delegate;
private CompressionAlgorithm algo; // GZIP, ZIP...
public CompressingExporter(DocumentExporter delegate, CompressionAlgorithm algo) {...}
public void export(Document doc, OutputStream out) {
try (OutputStream compressed = algo.wrap(out)) {
delegate.export(doc, compressed);
}
}
}
// 4. 客戶端組合 (運行時靈活配置)
DocumentExporter exporter = new CompressingExporter(new PdfExporter(), GZIP_ALGO);
exporter.export(doc, fileOut); // 導出壓縮 PDF
// 新增格式/壓縮算法:零修改現有代碼,僅新增實現類
掌握「接口+組合+模式」三位一體,復用從口號變為可執行的架構決策。