2009-11-14 34 views
39

我對Liskov替換原理的理解是,對於派生類,基類的某些屬性爲true或某些已實現的基類的行爲也應該爲真。Liskov替代原則 - 沒有重寫/虛擬方法?

我想這將意味着當一個方法在基類定義,它不應該在派生類overrided - 從那以後代的基類,而不是派生的類會給出不同的結果。我猜這也意味着,擁有(非純粹的)虛擬方法是一件壞事?

我想我可能對原理有一個錯誤的理解。如果我不這樣做,我不明白爲什麼這個原則是好的做法。誰可以給我解釋一下這個?謝謝

+1

謝謝大家誰回答。我認爲你們所有人都對我對這種工作方式的理解作出了很多貢獻。我給每個人一個投票,我不知道我怎麼能確定正確的答案(每個人的答案幫助我!:D) – Aishwar 2009-11-18 13:48:03

回答

-2

一個簡短的解釋(單擊此鏈接 - >)Liskov substitution principle是,如果您有一個基類BASE和SUB1和SUB2子類,其餘代碼應始終引用BASE和NOT SUB1和SUB2。
就是這樣,很簡單,就是不指代子類

+0

嘿,這是有道理的,我可以明白爲什麼這將是壞的。但是它的建議與通常用於解釋這一原則的方形和矩形示例所顯示的看起來有很大不同。如果你也可以用這個例子解釋這個,那將是非常有用的。 – Aishwar 2009-11-14 22:53:32

+2

@歐姆 - 我認爲你的意思是BASE的現有和未來客戶應該繼續參考BASE。代碼的其他部分可能非常需要引用SUB1和/或SUB2。 – quamrana 2009-11-14 22:57:14

+0

@歐姆:我同意quamrana,最初我對你的回答感到困惑 - 直到我讀到你提供的鏈接的內容。 – Aishwar 2009-11-14 23:05:06

6

不,它告訴你應該能夠以與基類相同的方式使用派生類。有很多方法可以在不破壞的情況下重寫方法。一個簡單的例子,C#中的GetHashCode()是基於所有類的,並且仍然可以將它們全部用作「對象」來計算哈希碼。據我所知,打破規則的典型例子是從矩形中派生出Square,因爲Square不能同時具有寬度和高度 - 因爲設置一個會改變另一個,因此它不再符合矩形規則。但是,您仍然可以使用.GetSize()來創建基本形狀,因爲所有形狀都可以這樣做 - 因此可以替換任何派生形狀並將其用作Shape。

+0

嘿,所以你說C#中的任何對象都可以調用GetHashCode(),並且可以將其轉換爲對象,而GetHashCode()將返回相同的值。還是我誤解了這個?我猜測GetHashCode是在Object中實現的,派生的對象不會覆蓋這個。我不明白這是如何對應於方形/矩形的例子。你能解釋一下嗎? – Aishwar 2009-11-14 22:59:13

+2

我認爲queen3意味着許多類需要重寫'GetHashCode()',但調用它的客戶端(如哈希表)不會知道它們沒有調用爲Object定義的方法。由於合同仍然滿足,所以沒有發生違規行爲。但是,使用Square/Rectangle示例,客戶端可能能夠告訴類已經傳遞了哪些類,或者可能以某種方式失敗,因此發生了違規*。 – quamrana 2009-11-14 23:16:37

+4

不,不是相同的值,而是滿足關於此值的基類合同的某個值。至於形狀和散列,請參閱派生SomeObject,如果您只能調用GetHashCode()_after_ ToString()(出於某種原因),那麼您打破契約 - 現在調用者必須知道它是對象還是您的SomeObject。現在,您無法將SomeObject傳遞給期望對象的調用者。與形狀相同 - 您無法將Square傳遞給期望Rectangle的調用者,因爲他們將嘗試設置Width並且不會期望它改變Height。你可以用一個班替換另一個班。因此,LSP被破壞。 – queen3 2009-11-15 09:39:41

45

子類覆蓋在基類的方法是完全通過里氏Substituion原理允許的。

這可能是簡化了太多,但我記得它作爲「子類應要求僅此而已,並承諾也不少」

如果一個客戶端使用超ABC與方法something(int i),那麼客戶應該能夠毫無問題地替換ABC的任何子類。不要以變量類型來考慮這個問題,也許應該根據前提條件和後置條件來考慮它。

如果高於我們在ABC基類something()方法有一個放鬆的前提條件,即允許任何整數,然後ABC必須的所有子類還允許任何整數。子類GreenABC不允許在something()方法中添加額外的前提條件,該方法要求該參數爲正數整數。這會違反Liskov替代原則(即需要更多)。因此,如果客戶使用子類BlueABC並將負整數傳遞給something(),那麼如果我們需要切換到GreenABC,客戶端將不會中斷。相反,如果基地ABCsomething()方法有一個後置條件 - 例如保證它不會返回零值 - 那麼所有的子類都必須服從相同的後置條件,否則他們違反了Liskov替代原則(即,承諾減)。

我希望這會有所幫助。

+0

嘿,所以如果通過用GreenABC代替ABC並調用'something'返回一個不同的值 - 是否違反了原則? – Aishwar 2009-11-14 22:49:50

+2

如果GreenABC返回的值對於ABC提供的規範有效,那麼不會違反原則。 – Grundlefleck 2009-11-14 23:00:33

+1

@Grundlefleck:對不起,如果我似乎沒有得到它。那麼,派生類不能改變基類的行爲,只能添加它呢?即我不能重寫派生類中的方法以從基類中獲得不同的實現。 – Aishwar 2009-11-15 00:19:11

1

我認爲你在你所描述的原理的方式從字面上正確,只覆蓋純虛,或抽象方法將確保你不違反它。

但是,如果你看一下從一個客戶點的原則,也就是說,需要對基類的引用的方法。如果此方法不能告訴(當然不嘗試,也不需要找出)類傳遞進來,那麼你也沒有違反原則的任何實例。因此,重寫基類方法可能無關緊要(某些裝飾器可能會這樣做,在進程中調用基類方法)。

如果客戶端似乎需要找出傳入的實例的類,那麼您將面臨維護的噩夢,因爲您應該在維護工作中添加新的類,而不是修改現有的例程。 (也OCP見)

+0

因此,在常用的正方形和矩形示例中,Square不能從Rectangle繼承。如果矩形的setWidth屬性試圖識別它是真實類型(如果它是一個傳遞給帶有Rectangle的函數的Square)並試圖實現該情況下的特殊邏輯,則這是違反規定的。它是否正確? – Aishwar 2009-11-14 23:10:45

+1

@ aip.cd.aish:如果客戶不能說出來,它不是違規,如果客戶必須使用特殊的邏輯來解決問題,這是違規行爲。如果類的方法不關心它們的類型是什麼,或者可以安全地假定它們的類型是最初定義方法的類型,它會好得多。 – quamrana 2009-11-14 23:32:59

+0

@quamrana:根據你對queen3的回答我的評論的回覆,派生類不能改變基類的行爲,只能添加它嗎?即我不能重寫派生類中的方法以從基類中獲得不同的實現。這是原則狀態(除了關於方法的部分不應該試圖確定它實際是什麼類)? – Aishwar 2009-11-15 00:24:11

2

重寫打破了里氏替換原則,如果你改變由基礎方法定義的任何行爲。這意味着:

  1. 的 子方法中最薄弱的前提應該是不低於基本方法更強 。
  2. 對孩子的方法 後置條件意味着對於 父方法後置條件。其中一個後置條件 由形成:一個)所有側 效果所造成的返回的表達的方法執行,並b)中 類型和值。

從你可能意味着在子方法的任何新功能,不影響什麼是從超預期的方法不違反原則,這兩個要求。這些條件允許您使用需要超類實例的子類實例。

如果這些規則沒有聽從類違反了LSP。一個典型例子是以下的層次結構:Point(x,y)類,類ColoredPoint(x,y,color)延伸Point(x,y)和重寫方法equals(obj)ColoredPoint反射由顏色平等。現在,如果有一個實例Set<Point>,他可以假設在這個集合中具有相同座標的兩個點是相等的。這是不是與覆蓋的方法equals,在一般的情況下,就是沒有辦法擴展了新類,並添加equals使用方法不破壞LSP的一個方面。

因此,每一個你打破這個原則時候你含蓄介紹,揭示了當不變的由代碼預期父類是不成立的潛在錯誤。然而,在現實世界中往往存在不違反LSP,所以可以使用,例如,@ViolatesLSP類註釋,警告客戶端,它是不是安全的多態病毒或任何其他使用類的實例沒有明顯的設計解決方案依賴於Liskov替代原理的情況。

+0

如果指定X.Equals(Y)必須返回true,如果X和Y是相同的實例,必須始終返回任何特定的完全構造的實例X和Y對的相同值,則必須返回false,如果X Y將永遠表現爲不同的對象實例,並且如果任何對X的引用組合可以被替換爲Y或反之,則可以返回true,而不影響程序行爲。可以肯定的是,Equals的很多.net實現不以那種方式工作,但是這樣的Equals定義將是有用的,並且完全符合LSP。 – supercat 2011-09-12 22:11:32

+1

問題在於,LSP並不意味着'如果X和Y會表現爲不同的對象實例',那麼'equals必須返回false',但只有後代必須像其父項一樣行爲。例如,如果將超類的成員放在散列表中作爲鍵,然後使用子類實例查找它,則不會找到它,因爲在您的情況下它們將不相同。 – 2011-09-13 01:27:47

+0

讓類定義一個等同關係通常是有用的,這個等價關係不像Object.Equals所暗示的那麼具體。這種等價關係只能用於從某種類型派生的對象。如果定義了一個等價關係,該關係指定兩個名稱相同的動物應該被認爲是相同的,那麼名爲「Fred」的SiameseCat不會與名爲「Fred」的Cat相比, ,一個名爲「Fred」的土豚,除非明確指定了一個等價關係,儘管... – supercat 2011-09-13 03:25:30

5

有它說,如果游泳如魚得水一個通俗的例子,嘎嘎喜歡鴨子,但需要電池,那麼它打破了里氏替換原則。

簡單地說,你有被人利用基地鴨類。然後,通過引入PlasticDuck添加層次結構,並使用相同的重複行爲(如游泳,嘎嘎等)。)作爲鴨子,但需要電池來模擬這些行爲。這基本上意味着您正在爲Sub Class的行爲引入一個額外的前提條件,要求電池執行與之前由Base Duck類沒有使用電池時相同的行爲。這可能會讓你的Duck類的用戶感到意外,並且可能會破壞圍繞Base Duck類的預期行爲而建立的功能。

這裏是一個很好的鏈接 - http://lassala.net/2010/11/04/a-good-example-of-liskov-substitution-principle/

相關問題