里氏替換原則是在作繼承設計時須要遵循的原則,不遵循了 LSP 的繼承類會帶來意想不到的問題。html
里氏替換原則(Liskov Substitution Principle) 是由 Barbara Liskov 在 1987 年提出來的,Liskov 是她的姓,國內翻譯成 里氏。java
原則聲明:若是類型 S 是類型 T 的子類型,那麼 T 類型的對象能夠替換成 S 類型的對象,而不會影響程序的行爲。ide
LSP 對語言增長了新的簽名約束(協變與逆變能夠看這篇文章Java中的逆變與協變):ui
從契約角度來看,里氏替換原則有4層含義:this
繼承描述的是 is-a
關係,開閉原則要求咱們使用繼承增長功能,LSP 原則是指導咱們如何繼承。編碼
在之前寫的一篇里氏替換原則 的文章裏,我提到過:.net
每一個類都會有public方法,有些類會實現interface,供其餘類使用,自身就處在一個服務的位置上。 每一個public方法都是自身所作出的一個承諾,只要你按照要求調用,就會提供正確的服務。 子類在繼承後,當然是得到了超類的帶來的‘財富’,更重要的是要遵照超類作出的承諾, 破壞了這個承諾其實是沒有資格繼承超類的。
若是破壞了繼承原則,那麼開閉原則也就沒法使用。子類不按照契約設定編碼,那就是在給使用者挖坑。翻譯
需求要求設計一個鳥的繼承體系,以下是咱們設計的抽象基類:設計
public abstract class Bird { private String name; public void setName(String name){ this.name = name; } public void fly() { System.out.println(name + " fly"); } }
大部分鳥在這個基類中都工做的很好,可是有一天來了一隻企鵝,企鵝是不會飛的,所以咱們重寫 fly
方法code
public class Penguin { @Override public void fly() { throw new RuntimeException(); } }
因爲企鵝不會飛,在 fly
方法裏直接拋出了異常。
注意,這裏已經違反了 LSP 原則,在基類中並無異常拋出,使用方正常使用,而在 Penguin
類中 fly
方法拋出了異常,違反了基類遵照的契約。
要解決這個問題,咱們須要應用接口分離原則來拆分 Bird
類,由 Penguin
來看, fly
功能並非 Bird
承擔的職責,應該將其單獨放到一個接口中,會飛的鳥自行實現。若是像上面那樣,大部分鳥都有一個默認的飛行實現,則咱們能夠作一個默認的飛行實現類,使用組合的方式放到會飛的鳥中。
public abstract class Bird { private String name; public void setName(String name){ this.name = name; } } public interface Flyable { public void fly(); }
里氏替換原則是繼承須要遵循的原則,有時咱們可能在無心中就已經違反了原則要求,一是由於咱們沒有意識到,二是咱們設計的接口、抽象基類有問題。遇到違反 LSP 原則的繼承,有兩招來解決:1. 修改實現,2。 更改設計。