多個類擁有相同字段(如 Id、CreateTime、IsDeleted),如何設計既避免重複代碼,又保持靈活擴展?
| 方案 | 適用場景 | 優點 | 缺點 | 代碼示例 |
|---|---|---|---|---|
| 基類繼承 | 強「是一種」關係,共享行為 | 代碼復用最簡單,多態天然支持 | 單繼承限制,耦合度高,脆弱基類問題 | class EntityBase { int Id; } class User : EntityBase {} |
| 組合/聚合 | 共享數據結構,無行為耦合 | 靈活、可運行時替換、符合「組合優於繼承」 | 需轉發調用,略顯繁瑣 | class AuditInfo { DateTime Created; } class Order { AuditInfo Audit; } |
| 接口 + 默認實現 | 定義契約,C# 8.0+ 支持默認方法 | 多實現、無狀態、可混入 | 無法定義實例字段(僅屬性),邏輯受限 | interface IAuditable { DateTime Created {get;} } |
| 擴展方法 | 為現有類型添加共用邏輯 | 不侵入原類型,語法糖友好 | 無法存儲狀態,僅行為復用 | static class EntityExt { public static bool IsNew(this IEntity e) => e.Id == 0; } |
EntityBase(含 Id、版本號、軟刪除)。AuditInfo、TenantInfo 作為值對象嵌入。IAuditable + 擴展方法提供默認行為。// 1. 基礎實體(繼承)
public abstract class EntityBase {
public int Id { get; protected set; }
public DateTime CreatedAt { get; protected set; } = DateTime.UtcNow;
public bool IsDeleted { get; protected set; }
}
// 2. 審計信息(組合 - 值對象)
public record AuditInfo(DateTime CreatedAt, string CreatedBy, DateTime? UpdatedAt, string UpdatedBy);
// 3. 實體使用組合
public class Order : EntityBase {
public string OrderNo { get; set; }
public AuditInfo Audit { get; private set; }
public void MarkCreated(string user) => Audit = new AuditInfo(DateTime.UtcNow, user, null, null);
}
// 4. 接口契約(可選)
public interface ISoftDeletable {
bool IsDeleted { get; }
void Delete();
}
public static class SoftDeleteExtensions {
public static void SafeDelete(this ISoftDeletable e) {
if (!e.IsDeleted) e.Delete();
}
}
BaseEntity → 導致「上帝基類」。「共同字段」不等於「共同繼承」 —— 用組合拆解橫切關注點,用接口定義契約,讓類層次清晰、可測試、可演化。