1. 程式人生 > 其它 >複用與里氏替換原則(LSP)

複用與里氏替換原則(LSP)

設計可複用的類

以面向物件程式設計的思想設計可複用的類的關鍵要點有:

  • 封裝與資訊隱藏
  • 子類繼承與重寫
  • 多型、子類與過載
  • 泛型
    有兩個基本原則:
  1. 行為子類與里氏替換原則
  2. 委託與組成

行為子類與里氏替換原則

行為子類

子類多型:對於不同型別的物件,使用者程式碼可用同意的方式處理。假設有一個類Dog是Animal型別的子類,那麼當存在一個適用於Dog型別的表示式,這個表示式也能被Animal型別的其他子類複用。例如:

Animal a = new Animal();
Animal d1 = new Dog();
Dog d2 = new Dog();

在可以使用Animal型別物件的場景下,使用d1與d2都沒有問題。

Java中的靜態型別檢查

  • 子類中可以增加父類沒有的方法,但是不可刪除父類中的方法。
  • 子類中需要實現所有未定義實現的方法。
  • 子類中的重寫方法必須返回與父類中原方法返回值相同的型別或其子類。
  • 子類中的重寫方法必須使用與父類宣告中相同型別的引數。
  • 子類中重寫的方法不能丟擲額外的異常。

子類中的規約性應與父類中一致或更強

A specification S2 is stronger than or equal to a specification S1 if and only if S2’s precondition is weaker than or equal to S1’s, and S2’s postcondition is stronger than or equal to S1’s, for the states that satisfy S1’s precondition.

里氏替換原則(LSP)

定義1:如果對每一個型別為S的物件o1,都有型別為T的物件o2,使得以T定義的所有程式P在所有的物件o1都代換成o2時,程式P的行為沒有發生變化,那麼型別S是型別T的子型別。
定義2:所有引用基類的地方必須能透明地使用其子類的物件。
具體來講:

  1. 子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法;
  2. 子類中可以增加自己特有的方法。

子類必須實現父類所有非私有的屬性和方法,或子類的所有非私有屬性和方
法必須在父類中宣告。即,子類可以有自己的“個性”,這也就是說,里氏
代換原則可以正著用,不能反著用(在專案中,採用里氏替換原則時,儘量
避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關係就
很難調和了)。根據里氏代換原則,為了保證系統的擴充套件性,在程式中通常
使用父類來進行定義,如果一個方法只存在子類中,在父類中不提供相應的
宣告,則無法在以父類定義的物件中使用該方法。
儘量把父類設計為抽象類或者介面。讓子類繼承父類或實現父介面,並實現
在父類中宣告的方法,執行時,子類例項替換父類例項,我們可以很方便地
擴充套件系統的功能,同時無須修改原有子類的程式碼,增加新的功能可以通過增
加一個新的子類來實現。

在程式設計中,LSP有如下限制:

  • 子類的前置條件(precondition)不能被強化
  • 子類的後置條件(Postcondition)不能被弱化
  • 子類中的不變數(Invariant)需與父類的保持一致

協變(Covariance)

協變是在電腦科學中,描述具有父/子型別關係的多個型別通過型別構造器、構造出的多個複雜型別之間是否有父/子型別關係的用語。例如:

class P { //父類
  Object a() {...} //返回值為Object
}

class C extends P { //子類
  @Override
  String a() {...} //方法的返回值更加具體
}

同理,對丟擲的異常,子類中丟擲的異常型別應該比父類中的更加具體。

逆變(Contravariance)

逆變即協變的對立面,協變要求型別更加具體,而逆變要求型別更加抽象。同樣以上面的程式碼為例子:

class P { //父類
  void a(String s) {...} //引數型別為String
}

class C extends P { //子類
  @Override
  void a(Object s) {...} //引數型別更加抽象,為String型別的父類Object
}

在LSP原則中,子類中的方法的返回值與異常的型別要求協變,方法的引數型別要求逆變。

泛型中的LSP

注意:儘管Integer是Number型別的子類,但是Collection 不是Collection型別的子類

泛型萬用字元
  1. 普通萬用字元,常規使用 `List`
  2. 下限萬用字元,規定使用的型別應為A及其父類
  3. 上限萬用字元,規定使用的型別應為A及其子類