java七大設計原則

1.開閉原則(Open Close Principle/OCP)

定義:一個類、模塊和函數應該對擴展開放,對修改關閉。java

  • 開放-封閉原則的意思就是說,你設計的時候,時刻要考慮,儘可能讓這個類是足夠好,寫好了就不要去修改了,若是新需求來,咱們增長一些類就完事了,原來的代碼能不動則不動。這個原則有兩個特性,一個是說「對於擴展是開放的」,另外一個是說「對於更改是封閉的」。面對需求,對程序的改動是經過增長新代碼進行的,而不是更改現有的代碼。這就是「開放-封閉原則」的精神所在

舉例說明什麼是開閉原則,以書店銷售書籍爲例,其類圖以下:
圖片描述
項目上線,書籍正常銷售,可是咱們常常由於各類緣由,要打折來銷售書籍,這是一個變化,咱們要如何應對這樣一個需求變化呢?
咱們有下面三種方法能夠解決此問題:算法

  • 修改接口
    在IBook接口中,增長一個方法getOffPrice(),專門用於進行打折處理,全部的實現類實現此方法。可是這樣的一個修改方式,實現類NovelBook要修改,同時IBook接口應該是穩定且可靠,不該該常常發生改變,不然接口做爲契約的做用就失去了。所以,此方案否認。
  • 修改實現類
    修改NovelBook類的方法,直接在getPrice()方法中實現打折處理。此方法是有問題的,例如咱們若是getPrice()方法中只須要讀取書籍的打折前的價格呢?這不是有問題嗎?固然咱們也能夠再增長getOffPrice()方法,這也是能夠實現其需求,可是這就有二個讀取價格的方法,所以,該方案也不是一個最優方案。
  • 經過擴展實現變化
    咱們能夠增長一個子類OffNovelBook,覆寫getPrice方法。此方法修改少,對現有的代碼沒有影響,風險少,是個好辦法(以下圖)。

圖片描述

爲何使用(好處)ide

  • 可複用性好。
    咱們能夠在軟件完成之後,仍然能夠對軟件進行擴展,加入新的功能,很是靈活。所以,這個軟件系統就能夠經過不斷地增長新的組件,來知足不斷變化的需求。如:只變化了一個邏輯,而不涉及其餘模塊,好比一個算法是abc,如今須要修改成a+b+c,能夠直接經過修改原有類中的方法的方式來完成,前提條件是全部依賴或關聯類都按照相同的邏輯處理。
  • 可維護性好。
    因爲對於已有的軟件系統的組件,特別是它的抽象底層不去修改,所以,咱們不用擔憂軟件系統中原有組件的穩定性,這就使變化中的軟件系統有必定的穩定性和延續性。如:一人模塊變化,會對其它的模塊產生影響,特別是一個低層次的模塊變化必然引發高層模塊的變化,所以在經過擴展完成變化。

如何實現函數

  • 實現開閉原則的關鍵就在於「抽象」。把系統/軟件的全部可能的行爲抽象成一個抽象底層,這個抽象底層規定出全部的具體實現必須提供的方法的特徵。做爲系統設計的抽象層,要預見全部可能的擴展,從而使得在任何擴展狀況下,系統的抽象底層不需修改;同時,因爲能夠從抽象底層導出一個或多個新的具體實現,能夠改變系統的行爲,所以系統設計對擴展是開放的。抽象是對一組事物的通用描述,沒有具體的實現,也就表示它能夠有很是多的可能性,能夠跟隨需求的變化而變化。所以,經過接口或抽象類能夠約束一組可能變化的行爲,而且可以實現對擴展開放,其包含三層含義:
    經過接口或抽象類約束擴散,對擴展進行邊界限定,不容許出如今接口或抽象類中不存在的public方法。
    參數類型,引用對象儘可能使用接口或抽象類,而不是實現類,這主要是實現里氏替換原則的一個要求。
    抽象層儘可能保持穩定,一旦肯定就不要修改。
    里氏替換原則(LSP)、依賴倒轉原則(DIP)、接口隔離原則(ISP)以及抽象類(Abstract Class)、接口(Interface)等等,均可以看做是開閉原則的實現方法。

2.里氏代換原則(Liskov Substitution Principle/LSP)

定義:全部引用基類(父類)的地方必須能透明地使用其子類的對象。通俗講:子類能夠擴展父類的功能,但不能改變父類原有的功能。spa

  • 里氏代換原則意思說,在軟件中將一個基類對象(父類)替換成它的子類對象,程序將不會產生任何錯誤和異常,反過來則不成立,若是一個軟件實體使用的是一個子類對象的話,那麼它不必定可以使用基類對象。里氏代換原則是實現開閉原則的重要方式之一,因爲使用基類對象的地方均可以使用子類對象,所以在程序中儘可能使用基類類型來對對象進行定義,而在程序運行時再肯定其子類類型,用子類對象來替換父類對象。
  • 例如:我喜歡動物,那我必定喜歡狗,由於狗是動物的子類;可是我喜歡狗,不能據此判定我喜歡動物,由於我並不喜歡老鼠,雖然它也是動物。

爲何使用(好處)設計

  • 里氏代換原則是實現開閉原則的重要方式之一,優勢同開閉原則同樣。

缺點code

  • 增長了對象之間的耦合性。所以在系統設計時,遵循里氏替換原則,儘可能避免子類重寫父類的方法,能夠有效下降代碼出錯的可能性。

實現原則對象

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

舉例逐個講解blog

子類能夠實現父類的抽象方法,可是不能覆蓋父類的非抽象方法。
在咱們作系統設計時,常常會設計接口或抽象類,而後由子類來實現抽象方法,這裏使用的其實就是里氏替換原則。子類能夠實現父類的抽象方法很好理解,事實上,子類也必須徹底實現父類的抽象方法,哪怕寫一個空方法,不然會編譯報錯。里氏替換原則的關鍵點在於不能覆蓋父類的非抽象方法。父類中凡是已經實現好的方法,其實是在設定一系列的規範和契約,雖然它不強制要求全部的子類必須聽從這些規範,可是若是子類對這些非抽象方法任意修改,就會對整個繼承體系形成破壞。如:類C1繼承類C時,能夠添加新方法完成新增功能,儘可能不要重寫父類C的方法。不然可能帶來難以預料的風險:繼承

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方法,違背里氏替換原則,引用父類的地方並不能透明的使用子類的對象,致使運行結果出錯。

子類中能夠增長本身特有的方法
在繼承父類屬性和方法的同時,每一個子類也均可以有本身的個性,在父類的基礎上擴展本身的功能。前面其實已經提到,當功能擴展時,子類儘可能不要重寫父類的方法,而是另寫一個方法,因此對上面的代碼加以更改,使其符合里氏替換原則,代碼以下:

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

當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參/入參)要比父類方法的輸入參數更寬鬆
代碼示例

import java.util.HashMap;
public class Father {
    public void func(HashMap m){
        System.out.println("執行父類...");
    }
}
 
import java.util.Map;
public class Son extends Father{
    public void func(Map m){//方法的形參比父類的更寬鬆
        System.out.println("執行子類...");
    }
}
 
import java.util.HashMap;
public class Client{
    public static void main(String[] args) {
        Father f = new Son();//引用基類的地方能透明地使用其子類的對象。
        HashMap h = new HashMap();
        f.func(h);
    }
}

運行結果:執行父類...
注意Son類的func方法前面是不能加@Override註解的,由於不然會編譯提示報錯,由於這並非重寫(Override),而是重載(Overload),由於方法的輸入參數不一樣。

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

import java.util.Map;
public abstract class Father {
    public abstract Map func();
}
 
import java.util.HashMap;
public class Son extends Father{
     
    @Override
    public HashMap func(){//方法的返回值比父類的更嚴格
        HashMap h = new HashMap();
        h.put("h", "執行子類...");
        return h;
    }
}
 
public class Client{
    public static void main(String[] args) {
        Father f = new Son();//引用基類的地方能透明地使用其子類的對象。
        System.out.println(f.func());
    }
}

執行結果:{h=執行子類...}

持續更新中。。。

相關文章
相關標籤/搜索