簡體   English   中英

Inheritance 或作文:依靠“is-a”和“has-a”?

[英]Inheritance or composition: Rely on "is-a" and "has-a"?

當我設計類並且必須在 inheritance 和組合之間進行選擇時,我通常使用經驗法則:如果關系是“is-a” - 使用 inheritance,如果關系是“has-a” - 使用組合。

總是正確的嗎?

謝謝你。

不 - “是一個”並不總是導致繼承。 引用得很好的例子是正方形和矩形之間的關系。 正方形是一個矩形,但設計從Rectangle類繼承Square類的代碼是不好的。

我的建議是用Liskov替換原則來增強你的“是一個/有一個”啟發式。 要檢查繼承關系是否符合Liskov替換原則,請詢問基類的客戶端是否可以在子類上運行,而不知道它在子類上運行。 當然,必須保留子類的所有屬性。

在正方形/矩形示例中,我們必須詢問矩形的客戶端是否可以在正方形上操作而不知道它是正方形。 客戶必須知道的是它在矩形上運行。 以下函數演示了一個客戶端,它假定設置矩形的寬度會使高度保持不變。

void g(Rectangle& r)
{
    r.SetWidth(5);
    r.SetHeight(4);
    assert(r.GetWidth() * r.GetHeight()) == 20);
}

這個假設適用於矩形,但不適用於正方形。 因此該函數不能在一個正方形上運行,因此繼承關系違反了Liskov Substitution原則。

其他例子

是的,不是。

線條可以模糊。 從OO早期的OO編程的一些非常可怕的例子來看,這沒有得到幫助:經理是員工是人。

你必須記住的關於繼承的事情是:繼承打破了封裝。 繼承是一個實現細節。 有關這個主題的各種文章。

最簡單的總結方法是:

喜歡成分。

這並不意味着將它用於完全排除繼承。 這只意味着繼承是一種后備立場。

如果關系是“is-a” - 使用繼承,如果關系是“has-a” - 使用組合。 總是對的嗎?

從某種意義上說,是的。 但是你必須小心不要引入不必要的,人為的“是一種”關系。

例如,有人可能認為ThickBorderedRectangle 是一個 Rectangle,初看起來似乎很合理,並決定使用繼承。 但是這種情況更好地描述了一個Rectangle 有一個邊框,它可能是也可能不是ThickBorder。 在這種情況下,他會更喜歡作曲。

以同樣的方式,人們可能會認為ThickBorder 是一個特殊的邊界並使用繼承; 但是最好說邊框寬度,因此更喜歡構圖。

在所有這些模棱兩可的案例中,我的經驗法則是三思而后行 ,正如其他人所建議的那樣, 更喜歡構成而不是繼承

我認為它不像“is-a”vs“has-a”那么簡單 - 正如克萊特斯所說,這條線可能變得非常模糊。 僅僅作為一個指導原則,並不是兩個人總能得出相同的結論。

正如克萊圖斯所說,更喜歡構成而不是繼承。 在我看來,必須有一個很好的理由從基類派生 - 特別是,基類確實需要設計用於繼承,實際上對於你想要應用的那種特化。

除此之外 - 這可能是一個不受歡迎的想法 - 它歸結為品味和直覺。 隨着時間的推移,我認為優秀的開發人員可以更好地了解繼承何時起作用以及什么時候不起作用,但他們可能很難清楚表達。 我知道覺得很難,正如這個答案所表明的那樣。 也許我只是把困難投射到其他人身上:)

繼承並不總是意味着存在“is-a”關系,缺乏一個並不總是意味着沒有。

例子:

  • 如果使用受保護或私有繼承,則會失去外部可替代性,也就是說,就層次結構之外的任何人而言,Derived不是一種Base。
  • 如果覆蓋基本虛擬成員並從引用Base的人那里改變可觀察的行為,從原始的Base實現中,那么您實際上已經破壞了可替代性。 即使你有一個公共繼承,heirarchy Derived is-not-a Base。
  • 在沒有任何繼承關系的情況下,你的班級有時可以替代另一個班級。 例如,如果您使用模板和相同的接口來獲得適當的const成員函數,那么Ellipse恰好是圓形的“Ellipse const&”可以替代“Circle const&”。

我在這里要說的是,無論你是否使用繼承,都需要花費很多心思和工作來維護兩種類型之間的“is-a”可替代關系。

沒有 是。 如果關系是“has-a”,請使用合成。 補充 :問題的原始版本說“使用繼承”兩者 - 一個簡單的拼寫錯誤,但糾正將我的回答的第一個詞從“否”改為“是”。)

並默認使用組合; 只在必要時使用繼承(但隨后不要猶豫使用它)。

人們常說繼承是一種“是一種”關系,但這會讓你陷入困境。 C ++中的繼承分為兩個概念:代碼重用和定義接口。

第一個說“我的類就像其他類一樣。我只是為它們之間的delta編寫代碼,並盡可能多地重用其他類的其他實現。”

第二個依賴於抽象基類,是類承諾實現的方法列表。

第一個可以非常方便,但如果做得不好也會導致維護問題,所以有些人會說你永遠不會這樣做。 大多數人都強調第二種,使用繼承來解釋其他語言明確稱之為接口的問題。

我認為約翰有正確的想法。 你把你的階級關系視為創建模型 ,這是他們開始教你OOP校長時他們試圖讓你做的事情。 真的,你最好看一下代碼架構方面的功能。 也就是說,對於其他程序員(或您自己)來說,最好的方法是在不破壞代碼的情況下重用代碼,或者必須查看實現以了解它的作用。

我發現繼承經常打破“黑匣子”的理想,所以如果信息隱藏很重要,它可能不是最好的方法。 我越來越發現,繼承對於提供通用接口比在其他地方重用組件更好。

當然,每種情況都是獨特的,沒有正確的方法來做出這個決定,只有經驗法則。

首先,我想說我完全同意加強 is-A 關系的投票最多的答案應該通過測試方法斷言以檢查 LSP 的一致性。

實際上,這個想法是由 Robert C 所著的《敏捷原則、模式、實踐》一書介紹的。 馬丁和彌迦馬丁]

書中介紹了一個啟發式方法,即子類不能比其基類 class 承擔更少的責任,這可以應用於著名的 Square-Rectangle 示例,以找出正方形不是矩形。

但是,我認為這是 OOP 解釋中最糟糕的例子之一。

這是因為正方形不超過 state 矩形 object 可以達到因此試圖找出 class 和其中一個實例之間的關系是錯誤的方法。

在現實世界中,在正方形之外,我們有更多的矩形 object 的風味狀態,如風景、肖像、16:9(1080P)、4:3(VGA) 等。

然而,一個程序對它們中的任何一個都沒有味道。

我的經驗法則(雖然不具體)是如果我可以為不同的類使用相同的代碼,那么我將該代碼放入父類並使用繼承。 否則我用組合物。

這使我編寫的代碼更少,本質上更容易維護。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM