面向對象五大原則-----里氏代換原則


  什么是里氏代換原則 

  里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承復用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被復用,而衍生類也能夠在基類的基礎上增加新

的行為。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關系就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規范。

  簡單的理解為一個軟件實體如果使用的是一個父類,那么一定適用於其子類,而且它察覺不出父類對象和子類對象的區別。也就是說,軟件里面,把父類都替換成它的子類,程序的行為沒有變化。

  但是反過來的代換卻不成立,里氏代換原則(Liskov Substitution Principle):一個軟件實體如果使用的是一個子類的話,那么它不能適用於其父類。

   舉個例子解釋一下這個概念

  先創建一個Person類

1 public class Person {
2     public void display() {
3         System.out.println("this is person");
4     }
5 }

  再創建一個Man類,繼承這個Person類

1 public class Man extends Person {
2 
3     public void display() {
4         System.out.println("this is man");
5     }
6     
7 }

  運行一下

 1 public class MainClass {
 2     public static void main(String[] args) {
 3         Person person = new Person();//new一個Person實例
 4         display(person);
 5         
 6         Person man = new Man();//new一個Man實例
 7         display(man);
 8     }
 9     
10     public static void display(Person person) {
11         person.display();
12     }
13 }

  可以看到

  運行沒有影響,符合一個軟件實體如果使用的是一個父類的話,那么一定適用於其子類,而且它察覺不出父類和子類對象的區別這句概念,這也就是java中的多態。

  而反之,一個子類的話,那么它不能適用於其父類,這樣,程序就會報錯

 1 public class MainClass {
 2     public static void main(String[] args) {
 3         Person person = new Person();
 4         display(person);//這里報錯
 5         
 6         Man man = new Man();
 7         display(man);
 8     }
 9     
10     public static void display(Man man) {//傳入一個子類
11         man.display();
12     }
13 }

 

  繼續再舉一個很經典的例子,正方形與長方形是否符合里氏代換原則,也就是說正方形是否是長方形的一個子類,

   以前,我們上學都說正方形是特殊的長方形,是寬高相等的長方形,所以我們認為正方形是長方形的子類,但真的是這樣嗎?

  

  從圖中,我們可以看到長方形有兩個屬性寬和高,而正方形則只有一個屬性邊長

  所以,用代碼如此實現

 1 //長方形
 2 public class Changfangxing{
 3     private long width;
 4     private long height;
 5     
 6     public long getWidth() {
 7         return width;
 8     }
 9     public void setWidth(long width) {
10         this.width = width;
11     }
12     public long getHeight() {
13         return height;
14     }
15     public void setHeight(long height) {
16         this.height = height;
17     }
18 }
 1 //正方形
 2 public class Zhengfangxing{
 3     private long side;
 4 
 5     public long getSide() {
 6         return side;
 7     }
 8 
 9     public void setSide(long side) {
10         this.side = side;
11     }
12 }

 

  可以看到,它們的結構根本不同,所以正方形不是長方形的子類,所以長方形與正方形之間並不符合里氏代換原則。

  當然我們也可以強行讓正方形繼承長方形

 1 //正方形
 2 public class Zhengfangxing extends Changfangixng{
 3     private long side;
 4 
 5     public long getHeight() {
 6         return this.getSide();
 7     }
 8 
 9     public long getWidth() {
10         return this.getSide();
11     }
12 
13     public void setHeight(long height) {
14         this.setSide(height);
15     }
16 
17     public void setWidth(long width) {
18         this.setSide(width);
19     }
20 
21     public long getSide() {
22         return side;
23     }
24 
25     public void setSide(long side) {
26         this.side = side;
27     }
28 }

 

   這個樣子,編譯器是可以通過的,也可以正常使用,但是這樣就符合里氏代換原則了嗎,肯定不是的。

  我們不是為了繼承而繼承,只有真正符合繼承條件的情況下我們才去繼承,所以像這樣為了繼承而繼承,強行實現繼承關系的情況也是不符合里氏代換原則的。

  但這是為什么呢?,我們運行一下

 1 public class MainClass {
 2     public static void main(String[] args) {
 3         Changfangxing changfangxing = new Changfangxing();
 4         changfangxing.setHeight(10);
 5         changfangxing.setWidth(20);
 6         test(changfangxing);
 7         
 8         Changfangxing zhengfangxing = new Zhengfangxing();
 9         zhengfangxing.setHeight(10);
10         test(zhengfangxing);
11     }
12     
13     public static void test(Changfangxing changfangxing) {
14         System.out.println(changfangxing.getHeight());
15         System.out.println(changfangixng.getWidth());
16     }
17 }

  結果:

  我們忽然發現,很正常啊,為什么不可以,但是我們繼續修改

 1 public class MainClass {
 2     public static void main(String[] args) {
 3         Changfangxing changfangxing = new Changfangxing();
 4         changfangxing.setHeight(10);
 5         changfangxing.setWidth(20);
 6         resize(changfangxing);
 7         
 8         Changfangxing zhengfangxing = new Zhengfangxing();
 9         zhengfangxing.setHeight(10);
10         resize(zhengfangxing);
11     }
12     
13     public static void test(Changfangxing changfangxing) {
14         System.out.println(changfangxing.getHeight());
15         System.out.println(changfangxing.getWidth());
16     }
17     
18     public static void resize(Changfangxing changfangxing) {
19         while(changfangxing.getHeight() <= changfangxing.getWidth()) {
20             changfangxing.setHeight(changfangxing.getHeight() + 1);
21             test(changfangxing);
22         }
23     }
24 }

  當長方形運行時,可以正常運行,而正方形則會造成死循環,所以這種繼承方式不一定恩能夠適用於所有情況,所以不符合里氏代換原則。

  還有一種形式,我們抽象出一個四邊形接口,讓長方形和正方形都實現這個接口

1 public interface Sibianxing {
2     public long getWidth();
3     public long getHeight();
4 }
 1 public class Changfangxing implements Sibianxing{
 2     private long width;
 3     private long height;
 4     
 5     public long getWidth() {
 6         return width;
 7     }
 8     public void setWidth(long width) {
 9         this.width = width;
10     }
11     public long getHeight() {
12         return height;
13     }
14     public void setHeight(long height) {
15         this.height = height;
16     }
17 }
 1 package com.ibeifeng.ex3;
 2 
 3 public class Zhengfangxing implements Sibianxing{
 4     private long side;
 5 
 6     public long getHeight() {
 7         return this.getSide();
 8     }
 9 
10     public long getWidth() {
11         return this.getSide();
12     }
13 
14     public void setHeight(long height) {
15         this.setSide(height);
16     }
17 
18     public void setWidth(long width) {
19         this.setSide(width);
20     }
21 
22     public long getSide() {
23         return side;
24     }
25 
26     public void setSide(long side) {
27         this.side = side;
28     }
29 }

  運行

 1 public class MainClass {
 2     public static void main(String[] args) {
 3         Changfangxing changfangxing = new Changfangxing();
 4         changfangxing.setHeight(10);
 5         changfangxing.setWidth(20);
 6         test(changfangxing);
 7         
 8         Zhengfangxing zhengfangxing = new Zhengfangxing();
 9         zhengfangxing.setHeight(10);
10         test(zhengfangxing);
11     }
12     
13     public static void test(Sibianxing sibianxing) {
14         System.out.println(sibianxing.getHeight());
15         System.out.println(sibianxing.getWidth());
16     }
17 }

  對於長方形和正方形,取width和height是它們共同的行為,但是給width和height賦值,兩者行為不同,因此,這個抽象的四邊形的類只有取值方法,沒有賦值方法。上面的例子中那個方法只會適用於不同的子類,LSP也就不會被破壞。

  注意事項

  在進行設計的時候,盡量從抽象類繼承,而不是從具體類繼承。如果從繼承等級樹來看,所有葉子節點應當是具體類,而所有的樹枝節點應當是抽象類或者接口。當然這個只是一個一般性的指導原則,使用的時候還要具體情況具體分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM