設計模式之里氏替換原則

里氏替換原則(Liskov Substitution Principle,簡稱LSP): 子類能夠替換父類

繼承有一些優勢:java

     1. 提升代碼的重用性,子類擁有父類的方法和屬性;
2. 提升代碼的可擴展性,子類可形似於父類,但異於父類,保留自個人特性;
缺點:侵入性、不夠靈活、高耦合
     1. 繼承是侵入性的,只要繼承就必須擁有父類的全部方法和屬性,在必定程度上約束了子類,下降了代碼的靈活性;
2. 增長了耦合,當父類的常量、變量或者方法被修改了,須要考慮子類的修改,因此一旦父類有了變更,極可能會形成
很是糟糕的結果,要重構大量的代碼。

 任何基類能夠出現的地方,子類必定能夠出現。里氏替換原則是繼承複用的基石,只有當衍生類能夠替換基類,軟件單位的功能不受到影響時,即基類隨便怎麼改動子類都不受此影響,那麼基類才能真正被複用編程

由於繼承帶來的侵入性,增長了耦合性,也下降了代碼靈活性,父類修改代碼,子類也會受到影響,此時就須要里氏替換原則。ide

  • 子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。
  • 子類中能夠增長本身特有的方法。
  • 當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。
  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

 

a.子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。spa

 

複製代碼
public class A {
    public void fun(int a,int b){
        System.out.println(a+"+"+b+"="+(a+b));
    }
}
 
public class B extends A{
    @Override
    public void fun(int a,int b){
        System.out.println(a+"-"+b+"="+(a-b));
    }
}
 
public class demo {
    public static void main(String[] args){
        System.out.println("父類的運行結果");
        A a=new A();
        a.fun(1,2);
        //父類存在的地方,能夠用子類替代
        //子類B替代父類A
        System.out.println("子類替代父類後的運行結果");
        B b=new B();
        b.fun(1,2);
    }
}
複製代碼
運行結果:
父類的運行結果
1+2=3
子類替代父類後的運行結果
1-2=-1

b.子類中能夠增長本身特有的方法。code

 

複製代碼
public class A {
    public void fun(int a,int b){
        System.out.println(a+"+"+b+"="+(a+b));
    }
}
 
public class B extends A{
    public void newFun(){
        System.out.println("這是子類的新方法...");
    }
}
 
public class demo {
    public static void main(String[] args){
        System.out.print("父類的運行結果:");
        A a=new A();
        a.fun(1,2);
        //父類存在的地方,能夠用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類後的運行結果:");
        B b=new B();
        b.fun(1,2);
        //子類B的新方法
        b.newFun();
    }
}
複製代碼
運行結果:
父類的運行結果:1+2=3
子類替代父類後的運行結果:1+2=3
這是子類的新方法...

c.當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。blog

 

複製代碼
public class LSP {


     class A {
        public void fun(HashMap map){
            System.out.println("父類被執行...");
        }
    }
     
     class B extends A{
        public void fun(Map map){
            System.out.println("子類被執行...");
        }
    }
     
     public static void main(String[] args){
            System.out.print("父類的運行結果:");
            LSP lsp =new LSP();
            LSP.A a= lsp.new A();
            HashMap<Object, Object> map=new HashMap<Object, Object>();
            a.fun(map);
            //父類存在的地方,能夠用子類替代
            //子類B替代父類A
            System.out.print("子類替代父類後的運行結果:");
            LSP.B b=lsp.new B();
            b.fun(map);
        }

}
複製代碼
運行結果:
父類的運行結果:父類被執行...
子類替代父類後的運行結果:父類被執行...
符合條件
咱們應當注意,子類並不是重寫了父類的方法,而是重載了父類的方法。由於子類和父類的方法的輸入參數是不一樣的。
子類方法的參數Map比父類方法的參數HashMap的範圍要大,因此當參數輸入爲HashMap類型時,只會執行父類的方法,不會執行父類的重載方法。這符合里氏替換原則。

複製代碼
 
 
//將子類方法的參數範圍縮小會怎樣?
import java.util.Map;
public class A {
    public void fun(Map map){
        System.out.println("父類被執行...");
    }
}
 
import java.util.HashMap;
public class B extends A{
    public void fun(HashMap map){
        System.out.println("子類被執行...");
    }
}
 
import java.util.HashMap;
 
public class demo {
     static void main(String[] args){
        System.out.print("父類的運行結果:");
        A a=new A();
        HashMap map=new HashMap();
        a.fun(map);
        //父類存在的地方,均可以用子類替代
        //子類B替代父類A
        System.out.print("子類替代父類後的運行結果:");
        B b=new B();
        b.fun(map);
    }
}
複製代碼
運行結果:
父類的運行結果:父類被執行...
子類替代父類後的運行結果:子類被執行...

在父類方法沒有被重寫的狀況下,子方法被執行了,這樣就引發了程序邏輯的混亂。
因此子類中方法的前置條件必須與父類中被覆寫的方法的前置條件相同或者更寬鬆。不符合裏式替換

 

d.當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。繼承

 

複製代碼
public class LSP1 {
     abstract class A {
            public abstract Map fun();
        }
         
     class B extends A{
            @Override
            public HashMap fun(){
                HashMap b=new HashMap();
                b.put("b","子類被執行...");
                return b;
            }
        }
         
         public static void main(String[] args){
                LSP1 lsp =new LSP1();
                LSP1.A a=lsp.new B();
                System.out.println(a.fun());
            }

}
複製代碼

 

運行結果:
{b=子類被執行...}ip

若在繼承時,子類的方法返回值類型範圍比父類的方法返回值類型範圍大,在子類重寫該方法時編譯器會報錯。ci

  看上去很難以想象,由於咱們會發如今本身編程中經常會違反里氏替換原則,程序照樣跑的好好的。因此你們都會產生這樣的疑問,假如我非要不遵循里氏替換原則會有什麼後果?編譯器

        後果就是:你寫的代碼出問題的概率將會大大增長。

相關文章
相關標籤/搜索