首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

繼承 vs 組合:Java 設計原則實戰與 BaseModel 反模式剖析

     編輯:Java 相關知識

核心概念辨析

關係關鍵字語意耦合度
繼承extendsIs-A(是一種),子類擁有父類所有行為與狀態強(白盒復用,破壞封裝)
組合成員變數Has-A(擁有),類別委託給內部物件完成功能弱(黑盒復用,保持封裝)

決策指南:何時用哪個?

  1. 僅為復用程式碼,且無明顯領域關係 → 組合。例如:工具類方法、通用邏輯封裝。
  2. 存在明確的「是一種」領域關係,且需多型替換 → 繼承(或介面 + 實作)。例如:Animal → Cat、PaymentStrategy → AlipayPayment。
  3. 慎用「屬性繼承」:父類定義通用欄位(如 id、createTime),子類繼承。建議:子類顯式重寫或改用組合(BaseEntity 作為嵌入欄位)。

反模式剖析:BaseModel 濫用

典型錯誤代碼

// 父類定義通用欄位
public class BaseModel {
    private Long id;
    private Date createTime;
    private Date updateTime;
    // getter/setter...
}

// 所有實體強制繼承
public class User extends BaseModel { /* ... */ }
public class Order extends BaseModel { /* ... */ }
public class Product extends BaseModel { /* ... */ }

看似優勢,實則陷阱

  • 省鍵盤? 現代 IDE 自動生成 getter/setter,節省極微。
  • 統一欄位? 實際上不同實體常需差異化:訂單需要 orderNo,用戶需要 email,強行統一反而增加無用欄位。

維護災難:IDE 查找引用失效

當你想查找 User.id 的使用位置:

  1. IDE 返回 BaseModel.id 所有子類的引用位置(Order.id、Product.id...)
  2. 大型專案中結果動輒 50+ 處,需人工逐一排查
  3. 重構時極易誤傷無關類別

根因:繼承將「偶然相同」的結構強行綁定為「必然相同」,違反介面隔離原則與單一職責原則。

正確姿勢:組合優於繼承

方案一:嵌入式基礎實體

// 獨立的基礎資料類(可為 record 或 Lombok @Data)
public class BaseEntity {
    private Long id;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    // getter/setter...
}

// 實體組合嵌入
@Entity
public class User {
    @EmbeddedId // 或 @Embedded + 獨立主鍵
    private BaseEntity base;

    private String username;
    private String email;
    // 業務欄位...
}

方案二:介面契約 + 預設實作

public interface Identifiable<ID> {
    ID getId();
    void setId(ID id);
}

public interface Auditable {
    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();
}

// 實體實作多個介面,各司其職
public class Order implements Identifiable<Long>, Auditable {
    private Long id;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;
    private String orderNo;
    // ...
}

優勢:編譯期約束、無強制繼承層級、支援多實作、查找引用精確。

進階原則:里氏替換原則檢驗

若繼承關係通過「子類型可替換父類型且不破壞行為」,則合理。測試:

  • 父類方法前置條件不能強化、後置條件不能弱化
  • 子類不拋出父類未聲明的新異常
  • 不可變性、歷史約束需保持

經典違規:Square extends Rectangle(設寬高邏輯衝突)、Penguin extends Bird(飛行行為缺失)。

總結:設計決策清單

  1. 是否存在真實的「Is-A」領域關係?否 → 組合
  2. 是否需要多型替換?否 → 組合
  3. 父類是否為「工具類」或「資料容器」?是 → 組合
  4. 是否違反里氏替換原則?是 → 重構為組合或介面
  5. 繼承層級是否超過 2-3 層?是 → 警惕,考慮扁平化

記住:繼承是強關係,組合是弱關係;弱關係系統更靈活、更穩健。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved