[英]Introducing a new subclass to an established system using inheritance which violates the Liskov Substitution Principle
問題:在將具有現有基本功能的子集的子類引入已建立的 inheritance 系統時,是否還有其他設計原則需要考慮?
上下文:我們有一個已建立的系統,可以對具有共享基礎功能的數十種不同類型的實體進行建模。 因此,我們有基礎數據庫表、基礎聚合、基礎助手、基礎驗證器等,然后是從基類繼承的實體類型特定子類。
我們被要求引入一種僅實現此基本功能的子集的新型實體。 據我了解,保留基本功能並僅覆蓋新實體的子類中受影響的屬性和方法將違反 Liskov 替換原則。 然而,徹底檢修系統似乎違反直覺:
僅針對一種新的實體類型,知道所有其他實體類型都需要以前共享的數據庫字段和基本實現。
在這種情況下,違反 LSP 是否可以接受? 還有其他設計原則適用於這種情況嗎?
詳細示例:考慮一個分配管理系統:
Assignment
,但都分配了一個Person
。AssignmentNotes
,允許將零到多個Notes
添加到Assignment
中。AssignmentNotes
還允許將Assignment
標記為帶有可選注釋的組Assignment
,並識別參與Assignment
的Person
。 每種分配類型都有 Aggregate、Helper 和 Validator 類,但由於共享功能,它們分別繼承自BaseAssignmentAggregate
、 BaseAssignmentHelper
和BaseAssignmentValidator
。 BaseAssignmentAggregate
負責加載和更新共享數據,例如:
public virtual async Task Load(int id)
{
Model = await Context.Assignment.SingleAsync(a => a.Id == id);
await Context.AssignmentNotes.Where(a => a.AssignmentId == id)
.Include(an => an.AssignmentNotesNote)
.Include(an => an.AssignmentNotesGroupAssignmentIncludedPerson)
.LoadAsync();
}
public async Task SaveAssignmentNotes(IReadOnlyCollection<int> noteIds, bool isGroupAssignment, string groupAssignmentComments, IReadOnlyCollection<int> groupAssignmentIncludedPersonIds)
{
AssertIsLoaded();
if (noteIds == null) throw new ArgumentNullException(nameof(noteIds));
...
// synchronise notes
...
Model.AssignmentNotes.IsGroupAssignment = groupAssignmentComments;
Model.AssignmentNotes.GroupAssignmentComments = groupAssignmentComments;
// synchronise group assignment people
...
// save
await SaveChanges();
}
DbContext 中的相關實體類型可能如下所示:
public partial class Assignment
{
public int Id { get; set; }
public byte AssignmentTypeId { get; set; }
public int AssignedPersonId { get; set; }
public virtual Person AssignedPerson { get; set; }
public virtual AssignmentNotes AssignmentNotes { get; set; }
public virtual AssignmentType AssignmentType { get; set; }
public virtual AssignmentTypeADetails AssignmentTypeADetails { get; set; }
public virtual AssignmentTypeBDetails AssignmentTypeBDetails { get; set; }
public virtual AssignmentTypeCDetails AssignmentTypeCDetails { get; set; }
public virtual AssignmentTypeDDetails AssignmentTypeDDetails { get; set; }
...
}
public partial class Person
{
public Person()
{
Assignment = new HashSet<Assignment>();
AssignmentNotesGroupAssignmentIncludedPerson = new HashSet<AssignmentNotesGroupAssignmentIncludedPerson>();
}
public int PersonId { get; set; }
public string Name { get; set; }
public virtual ICollection<Assignment> Assignment { get; set; }
public virtual ICollection<AssignmentNotesGroupAssignmentIncludedPerson> AssignmentNotesGroupAssignmentIncludedPerson { get; set; }
}
public partial class AssignmentNotes
{
public AssignmentNotes()
{
AssignmentNotesNote = new HashSet<AssignmentNotesNote>();
AssignmentNotesGroupAssignmentIncludedPerson = new HashSet<AssignmentNotesGroupAssignmentIncludedPerson>();
}
public int AssignmentId { get; set; }
public bool IsGroupAssignment { get; set; }
public string GroupAssignmentComments { get; set; }
public virtual Assignment Assignment { get; set; }
public virtual ICollection<AssignmentNotesNote> AssignmentNotesNote { get; set; }
public virtual ICollection<AssignmentNotesGroupAssignmentIncludedPerson> AssignmentNotesGroupAssignmentIncludedPerson { get; set; }
}
public partial class AssignmentNotesNote
{
public int Id { get; set; }
public int AssignmentId { get; set; }
public int NoteId { get; set; }
public virtual Note Note { get; set; }
public virtual AssignmentNotes Assignment { get; set; }
}
public partial class AssignmentNotesGroupAssignmentIncludedPerson
{
public int Id { get; set; }
public int AssignmentId { get; set; }
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public virtual AssignmentNotes Assignment { get; set; }
}
現在假設我們被要求引入一個新的AssignmentTypeZ
,它永遠不能是一個組分配,即這個 AssignmentType 仍然可以有注釋,但它永遠不能是IsGroupAssignment
,它永遠不能有GroupAssignmentComments
並且它永遠不會有任何AssignmentNotesGroupAssignmentIncludedPerson
。
這些屬性現在存在於AssignmentNotes表和所有相關的基類上是不是無效了? 是否涉及其他設計原則,或者我現在有義務
AssignmentNotesGroupAssignment
數據庫表?BaseAssignmentAggregate
更改為不加載此表或在其上實現屬性,並覆蓋所有其他 AssignmentTypeAggregates 來這樣做?BaseAssigmentAggregate
更改為僅更新 Notes,並覆蓋所有其他 AssignmentTypeAggregates 以更新AssignmentNotesGroupAssignment
詳細信息?只是為了一種新的實體類型?
這些屬性現在存在於AssignmentNotes表和所有相關的基類上是不是無效了?
我認為在對象和數據之間划清界限很重要。 最常見的軟件應用程序設計可能是“數據庫驅動設計”,其中首先設計數據庫模式,每個“對象”只是一個數據庫表的表示。 然而,這是執行 OOP 的糟糕方法,因為數據庫表保留數據,而不是對象。 表不會持久化 inheritance 或多態性或封裝或作為 OOP 中的 object 核心的任何業務邏輯。
OO 應用程序設計應該獨立於持久性,因為同一個應用程序可以在 NoSQL 或 RDBMS 或平面文件或從 a.network 提取的 JSON 數據之上運行。 沒關系。
這是一個冗長的咆哮,說 LSP 不關心 AssignmentNotes 表。
當涉及到AssignmentNotes
class 時,LSP 對於IsGroupAssignment
總是返回 false 並且從不填充GroupAssignmentComments
或AssignmentNotesGroupAssignmentIncludedPerson
的子類沒有問題。 畢竟,另一個子類在同一個 state 中也是有效的,即使它也允許其他狀態。
LSP 問題源於此方法簽名: SaveAssignmentNotes(IReadOnlyCollection<int> noteIds, bool isGroupAssignment, string groupAssignmentComments, IReadOnlyCollection<int> groupAssignmentIncludedPersonIds)
。
即使在添加建議的子類之前,該方法似乎也有問題,因為isGroupAssignment
參數指示是否可以填充最后兩個參數,這意味着該方法已經可以使用 arguments 的無效組合調用。
我會把這個方法一分為二。
SaveAssignmentNotes(IReadOnlyCollection<int> noteIds)
SaveAssignmentNotes(IReadOnlyCollection<int> noteIds, string groupAssignmentComments, IReadOnlyCollection<int> groupAssignmentIncludedPersonIds)
理想情況下,這兩種方法應位於不同的基類中:一種支持組分配,另一種不支持。 如果您將它們保留在同一個 class 中,請將第二個記錄為可選,用於支持組的AssignmentNotes
。
無論哪種方式,Liskov 的解決方案始終是清楚地記錄前置條件、后置條件和不變量。 LSP 是一種句法和語義原則,這意味着 API 合同不僅是方法簽名,而且是其文檔。 基礎 class 可以定義可選行為(在本例中用於保存“不受支持”的字段),但它還必須為未實現可選行為的子類定義預期行為,因此客戶不會對該子類感到驚訝。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.