1. 程式人生 > 其它 >設計模式之里氏替換原則示例

設計模式之里氏替換原則示例

里氏替換原則強調的是設計和實現要依賴於抽象而非具體;子類只能去擴充套件基類,而不是隱藏或者覆蓋基類,它包含4層含義.

一、里氏替換4原則

  1、子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法

  子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法,父類中凡是已經實現好的方法(相對於抽象方法而言),實際上是在設定一系列的規範和契約,雖然它不強制要求所有的子類必須遵從這些契約,但是如果子類對這些非抽象方法任意修改,就會對整個繼承體系造成破壞。舉例:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}

public class C1 extends C{
    @Override
    public int func(int a, int b) {
        return a-b;
    }
}

public class Client{
    public static void main(String[] args) {
        C c = new C1();
        System.out.println("2+1=" + c.func(2, 1));
    }
}

  執行結果:2+1=1

  上面的執行結果明顯是錯誤的。類C1繼承C,後來需要增加新功能,類C1並沒有新寫一個方法,而是直接重寫了父類C的func方法,違背里氏替換原則,引用父類的地方並不能透明的使用子類的物件,導致執行結果出錯。

  2、子類可以有自己的個性

  在繼承父類屬性和方法的同時,每個子類也都可以有自己的個性,在父類的基礎上擴充套件自己的功能。前面其實已經提到,當功能擴充套件時,子類儘量不要重寫父類的方法,而是另寫一個方法,所以對上面的程式碼加以更改,使其符合里氏替換原則,程式碼如下:

public class C {
    public int func(int a, int b){
        return a+b;
    }
}

public class C1 extends C{
    public int func2(int a, int b) {
        return a-b;
    }
}

public class Client{
    public static void main(String[] args) {
        C1 c = new C1();
        System.out.println("2-1=" + c.func2(2, 1));
    }
}

  執行結果:2-1=1

  3、覆蓋或實現父類的方法時輸入引數可以被放大

  當子類的方法過載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入引數更寬鬆,通過程式碼來講解一下:

public class ParentClazz {
    public void say(CharSequence str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(String str) {
        System.out.println("child execute say " + str);
    }
}

/**
 * 測試
 */
public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");

    }
}

// 執行結果:
// parent execute say hello
// child execute say hello

  以上程式碼中我們並沒有重寫父類的方法,只是過載了同名方法,具體的區別是:子類的引數 String 實現了父類的引數 CharSequence。此時執行了子類方法,在實際開發中,通常這不是我們希望的,父類一般是抽象類,子類才是具體的實現類,如果在方法呼叫時傳遞一個實現的子類可能就會產生非預期的結果,引起邏輯錯誤,根據里氏替換的子類的輸入引數要寬於或者等於父類的輸入引數,我們可以修改父類引數為String,子類採用更寬鬆的 CharSequence,如果你想讓子類的方法執行,就必須覆寫父類的方法。程式碼如下:

public class ParentClazz {
    public void say(String str) {
        System.out.println("parent execute say " + str);
    }
}

public class ChildClazz extends ParentClazz {
    public void say(CharSequence str) {
        System.out.println("child execute say " + str);
    }
}

public class Main {
    public static void main(String[] args) {
        ParentClazz parent = new ParentClazz();
        parent.say("hello");
        ChildClazz child = new ChildClazz();
        child.say("hello");
    }
}

// 執行結果:
// parent execute say hello
// parent execute say hello

  4、覆寫或實現父類的方法時輸出結果可以被縮小

  當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。程式碼實現案例如下:

public abstract class Father {
    public abstract Map hello();
}

public class Son extends Father {
    @Override
    public Map hello() {
        HashMap map = new HashMap();
        System.out.println("son execute");
        return map;
    }
}

public class Main {
    public static void main(String[] args) {
        Father father = new Son();
        father.hello();
    }
}

// 執行結果:
// son execute

二、里氏替換原則優點

  保證了父類的複用性,同時也能夠降低系統出錯誤的故障,防止誤操作,同時也不會破壞繼承的機制,這樣繼承才顯得更有意義。

  增強程式的健壯性,版本升級是也可以保持非常好的相容性.即使增加子類,原有的子類還可以繼續執行.在實際專案中,每個子類對應不同的業務含義,使用父類作為引數,傳遞不同的子類完成不同的業務邏輯,完美!

三、里氏替換原則總結

  繼承作為面向物件三大特性之一,在給程式設計帶來巨大便利的同時,也帶來了弊端。比如使用繼承會給程式帶來侵入性,程式的可移植性降低,增加了物件間的耦合性,如果一個類被其他的類所繼承,則當這個類需要修改時,必須考慮到所有的子類,並且父類修改後,所有涉及到子類的功能都有可能會產生故障。

  里氏替換原則的目的就是增強程式健壯性,版本升級時也可以保持非常好的相容性。

  有人會說我們在日常工作中,會發現在自己程式設計中常常會違反里氏替換原則,程式照樣跑的好好的。所以大家都會產生這樣的疑問,假如我非要不遵循里氏替換原則會有什麼後果?後果就是:你寫的程式碼出問題的機率將會大大增加