簡體   English   中英

省略super()和/或*弱化*先決條件是否違反Liskov替換原則?

[英]Does omitting super() and/or *weakening* preconditions violate the Liskov Substitution Principle?

我最近一直在深入研究一些SOLID設計原則,我從一個來源獲得的一些信息最初對我有意義,但基於我已經能夠找到的針對thr LSP的嚴格定義,它似乎就是這樣的信息可能不正確。 信息具體是:

1)在重寫方法上沒有回調到super()會違反LSP(或者至少會打開你的違規行為),因為基類的行為可能會在某些時候發生變化,而你的子類可能會以導致子類不再可替代父類。 這對我來說似乎有意義,但是如果有人可以詳細說明/給出一些關於什么時候不適合不回電話的信息那就太棒了。

2)子類的限制不應低於父類。 示例是:如果您的父類只接受正整數,那么您創建一個接受正負int的子類。 因此,孩子應該在父母的位置上工作正常,但在這種情況下,孩子不能委托給超級。

我認為這是有道理的,但LSP上的信息似乎恰恰相反:孩子無法強化前提條件。 兩者似乎都對我有意義,但利斯科夫只是說前提條件不能加強,后置條件不能削弱。 有人可以幫助我這個嗎?

1)情況,當不適合不回電話時

通常(但並非總是)這種情況意味着類層次結構設計出了問題。

如果子方法接受正確的輸入類型並返回正確的輸出類型,則不調用超類實現不會違反LSP。 它只表明問題的可能性。

當你不調用super方法時,這是一個絕對有效的例子:

class Animal

   void eat(Food food)
       // Eat the food

class Cat extends Animal

   void meow()
       // Say meow


class AnimalOwner

   Animal findAPet() 
       return new Animal()

class CatOwner

   // we can return the subclass of Animal here
   Cat findAPet() 
       return new Cat() // We don't need to use the parent implementation

這里CatOwner::findAPet()返回一個Cat (一個Animal子類),這在LSP方面是有效的,我們不調用父實現。

請注意,調用父實現並不能保證您不會遇到與不調用它時相同的問題。

考慮這個例子:

 class Child

    Output doSomething()
        parent = super::doSomething()
        if parent->isNotGood():
           return new OutputSubclass()  // We called super, but we return 
                                        // something different, might be not safe
        else:
           return parent                // We called super and return 
                                        // the same value, safe

2)“在子類型中不能加強先決條件”。 這也意味着前提條件(與輸入參數相關的期望)可以保持不變或被削弱。 在你提到的例子中,前提條件實際上被削弱了,所以沒有沖突:

Parent::doSomething(PositiveInteger value)  // Only positive integers

Child::doSomething(Integer value)           // Positive or negative integers, 
                                            // pre-condition is weaker 
                                            // (covers wider area of values)

不正確的是第一句話:“子類不應該比父母更少限制”。 當我們討論前置條件(輸入參數)時,子類可以減少限制,這就是示例中顯示的內容。

Liskov替換原則要求您可以在期望基類型時使用子類型。 為此,您必須遵守基本類型的合同。 對於具有方法f,前提條件Pre,postcondition Post和不變I的基類B,這意味着

  • 調用者代碼僅保證Pre在可能使用您的對象時保留。 如果Pre持有,你的先決條件也必須成立。 Logically Pre意味着派生方法的前提條件。 這意味着你可以擴大前置條件,因為Pre - >(Pre和condition),只要沒有矛盾
  • 調用者代碼期望Post在方法完成時保留。 這意味着您可以添加更多保證,但不得削弱后置條件。
  • 子類的不變量必須與基類的不變量兼容。

我會考慮顯式調用基類實現代碼氣味(構造函數除外)。 使用te,plate方法模式 (或C ++中的非虛擬接口 )來強制執行基類合約要好得多。 在Python中,這看起來像:

class Base:
   def publicMethod(self, x):
       // do something
       self.templateMethod(x)
       // do something else

   def templateMethod(self, x):
       // has to be overriden by sub-classes
       // it provides extension hooks, but
       // publicMethod ensures that the overall
       // functionality is correct

暫無
暫無

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

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