大型Java進階專題(二) 軟件架構設計原則(上)

前言

​ 今天開始咱們專題的第一課了,也是我開始進階學習的第一天,咱們先從經典設計思想開始,看看大牛市如何寫代碼的,提高技術審美、提升核心競爭力。本章節參考資料書籍《Spring 5核心原理》中的第一篇 Spring 內功心法(沒有電子檔,都是我取其精華並結合本身的理解,一個字一個字手敲出來的)。java

開閉原則

開閉原則(Open-Closed Principle,OCP)是指一個軟件實體(如類,模塊和函數)應該對擴展開發,對修改關閉。所謂的開閉,也正是對擴展和修改的兩種行爲的一個原則。它強調的是用抽象構建框架,用實現擴展細節,能夠提升軟件系統的可複用性及可維護性。開閉原則他是面向對象設計中最基礎的設計原則。它知道咱們如何創建穩定、靈活的系統。例如版本更新,咱們儘量不修改源碼,但能夠增長新功能。常見於咱們常常寫的接口與實現類的使用上,先接口定義好,對於不一樣的需求寫不一樣的實現類,後期有改動若不修改原有代碼,能夠從新寫一個實現類。

依賴倒置原則

依賴倒置原則(Dependence Inversion Principle,DIP)是指設計代碼結構時,高層模塊不該該依賴底層模塊,兩者都應該依賴其抽象。抽象不該該依賴細節,細節應該依賴抽象。經過依賴倒置,能夠減小類與類之間的耦合性,提升系統的穩定性,提升代碼的可讀性和可維護性,而且可以下降修改程序所形成的風險。這是一個比較重要的設計原則,在咱們平常開發中,常常有使用該思想的場景,能避免不少時候業務更改時,只須要改動少許代碼就能夠完成需求。下面咱們經過一個例子,來深刻理解該思想。

以學習課程爲例:編程

//Tom正在學習兩個課程
public class Tom {
    
    public void studyJva() {
        System.out.println("正在學習Java");
    }

    public void studyPython() {
        System.out.println("正在學習Python");
    }
}
//這裏調用Tom的兩個學習方法
public static void main(String[] args) {
        Tom tom = new Tom();
        tom.studyJva();;
        tom.studyPython();
}

​ 看上去以上兩個類的方法與調用沒有毛病,可是隨着業務的擴展,Tom又想繼續學習Go語言,這時候咱們就須要從低層到高層(調用層)依次修改代碼。先在Tom裏面正在studyGo()方法,而後在main中加以調用。這樣一來,系統發佈之後,實際上很是不穩定,如今代碼少能清晰知曉,可是實際項目中,代碼量、涉及的業務很是多,修改多個類的代碼,可能會代碼意想不到的風險。架構

​ 接下來咱們根據依賴倒置的思想來優化代碼,先建立一個ICourse接口。框架

public interface ICourse {
    /**
     * 抽象出來一個專門用來學習的方法,抽象不依賴細節,不去關心去學習什麼課程
     */
    void study();
}
//建立一個Java課程學習
public class JavaCourse implements ICourse {
    /**
     *由實現類去以爲具體學習什麼課程(細節應該依賴抽象)
     */
    public void study() {
        System.out.println("Tom在學習Java");
    }
}
//建立一個Python課程學習
public class PythonCourse implements ICourse {
    public void study() {
        System.out.println("Tom在學習Python");
    }
}
//修改Tom
public class Tom {
    public void study(ICourse course){
        //應證了高層模塊不該該依賴底層模塊,應該依賴其抽象
        course.study();
    }
}
//調用方代碼
public static void main(String[] args) {
        Tom tom = new Tom();
        tom.study(new JavaCourse());
        tom.study(new PythonCourse());
}

​ 經過以上代碼改造,能夠看出了依賴倒置原則的核心思想。經過抽象課程學習的接口,減小了類與類之間的耦合性和可維護性。當又有新的課程添加,咱們直接能夠再添加一個實現類,經過傳參的方式告知Tom,而不在須要修改底層代碼(也體現了開閉原則)。ide

​ 你們要切記:以抽象爲基準比以細節爲基準搭建起來的架構要穩定的多,所以在拿到需求以後,要面向接口編程,先頂層再細節設計代碼結構。函數

單一職責原則

​ 單一職責(Simple Responsibility Pinciple,SRP)是指不要存在多於一個致使類變動的緣由。假設咱們有一個類負責兩個職責,一旦發生需求變動,修改其中一個職責的代碼,有可能致使另一個職責的功能發生故障。這樣一來,這個類就存在兩個致使類發生變動的緣由。如何解決這個問題呢?將兩個職責用兩個類來實現,進行解耦。後期需求變動維護互不影響。這樣的設計,能夠下降類的複雜度,提升類的可讀性,提升系統的可維護性,下降變動引發的風險。整體來講,就是一個類、接口或方法只負責一項職責。學習

​ 接下來,咱們來看代碼示例,還用課程舉例,咱們的課程有直播課和錄播課。直播課不能快進和快退,錄播課程能夠任意反覆觀看,功能職責不同,仍是想建立一個Course類:優化

public class Course {
    public void study(String courseName){
        if ("直播課".equals(courseName)){
            System.out.println(courseName + "不能快進");
        }else {
            System.out.println(courseName + "能夠反覆回看");
        }
    }
}
//看下調用代碼
public static void main(String[] args) {
        Course course = new Course();
        course.study("直播課");
        course.study("錄播課");
}

​ 從上面的的代碼來看,Course類承擔了兩種處理邏輯。假如如今要對課程進行加密,直播課與錄播課的加密邏輯不同,必須修改代碼。而修改代碼的邏輯勢必會相互影響,容易帶來不可控的風險。咱們對職責進行解耦,來看代碼,分別建立兩個類加密

//直播
public class LiveCourse {
    public void study(String courseName){
        System.out.println(courseName + "不能快進看");
    }
}
//錄播
public class ReplayCourse {
    public void study(String courseName){
        System.out.println(courseName + "能夠反覆回看");
    }
}
//調用代碼
public static void main(String[] args) {
    LiveCourse liveCourse = new LiveCourse();
    liveCourse.study("直播課!");

    ReplayCourse replayCourse = new ReplayCourse();
    replayCourse.study("錄播課!");
}

​ 此時業務繼續發展,課程要作權限。沒有付費的學員能夠得到課程基本信息,已經付費的學員能夠得到視頻流,即學習權限。設計一個頂層接口,建立ICourse接口:設計

public interface ICourse {
    //獲取課程基本信息
    String getCourseName();
    //獲取視頻流
    byte[] getCourseVideo();
    //學習課程
    void studyCourse();
    //退款
    void refundCourse();
}

​ 其實在控制課程層面上至少有兩個職責。咱們能夠把展現職責和管理職責分離開來,都實現同一個抽象依賴。咱們能夠把這個接口拆成兩個接口:ICourseInfo和ICourseManager。

public interface ICourseInfo {
    //獲取課程基本信息
    String getCourseName();
    //獲取視頻流
    byte[] getCourseVideo();
}

public interface ICourseManager {
    //學習課程
    void studyCourse();
    //退款
    void refundCourse();
}

咱們看下類圖

以上是類/接口的單一職,下面咱們看下方法層面的單一職責,有時候咱們會偷懶,把一個方法寫成下面這樣:

/**
     * 修改用戶信息
     */
    private void modityUserInfo(String userName,String address){
        userName="LaoWang";
        address = "BeiJin";
    }

    private void modityUserInfo(String userName,String... fileds){
        userName="LaoWang";
        for (String filed : fileds) {
            //....
        }
    }

    private void modityUserInfo(String userName,String address,boolean bool){
        if (bool){
            //...
        }else {
            //...
        }
        userName = "LaoWang";
        address = "BeiJin";
    }

​ 顯然,上面的modityUserInfo()方法承擔了多個職責,既能夠修改userName又能夠修改address,甚至更多,明顯不符合單一職責。咱們作以下修改,能夠拆成兩個方法

private void modifyUserName(String userName){
        userName="LaoWang";
    }

    private void modifyAddress(String address){
        address = "BeiJin";
    }

​ 修改以後,開發起來簡單,維護起來也容易。咱們在實際開發中會有項目依賴、組合、聚合這些關係,還有的項目的規模、週期、技術人員的水平、對進度的把控,不少類都不符合單一職責。可是,咱們在編寫代碼的過程當中,儘量的讓接口和方法保持單一職責,對項目後期的維護是有很大的幫助的。

相關文章
相關標籤/搜索