天天看點

軟體設計的七大原則 --開閉原則 裡氏替換原則 依賴倒置原則

在軟體開發中,為了提高軟體系統的可維護性和可複用性,增加軟體的可擴充性和靈活性,程式員要盡量根據 7 條原則來開發程式,進而提高軟體開發效率、節約軟體開發成本和維護成本。

這 7 種設計原則是軟體設計模式必須盡量遵循的原則,各種原則要求的側重點不同。其中,開閉原則是總綱,它告訴我們要對擴充開放,對修改關閉;裡氏替換原則告訴我們不要破壞繼承體系;依賴倒置原則告訴我們要面向接口程式設計;單一職責原則告訴我們實作類要職責單一;接口隔離原則告訴我們在設計接口的時候要精簡單一;迪米特法則告訴我們要降低耦合度;合成複用原則告訴我們要優先使用組合或者聚合關系複用,少用繼承關系複用。

1、開閉原則

原則定義:

軟體實體應當對擴充開放,對修改關閉,這就是開閉原則的經典定義。

這裡的軟體實體包括以下幾個部分:
1.項目中劃分出的子產品
2.類與接口
3.方法

開閉原則的含義是:當應用的需求改變時,在不修改軟體實體的源代碼或者二進制代碼的前提下,可以擴充子產品的功能,使其滿足新的需求。
           

實作原理:

可以通過“抽象限制、封裝變化”來實作開閉原則,即通過接口或者抽象類為軟體實體定義一個相對穩定的抽象層,而将相同的可變因素封裝在相同的具體實作類中。

因為抽象靈活性好,适應性廣,隻要抽象的合理,可以基本保持軟體架構的穩定。而軟體中易變的細節可以從抽象派生來的實作類來進行擴充,當軟體需要發生變化時,隻需要根據需求重新派生一個實作類來擴充就可以了。
           

執行個體展示:

/**
 * 定義課程接口
 */
public interface ICourse {
    String getName();  // 擷取課程名稱
    Double getPrice(); // 擷取課程價格
    Integer getType(); // 擷取課程類型
}

/**
 * 英語課程接口實作
 */
public class EnglishCourse implements ICourse {

    private String name;
    private Double price;
    private Integer type;

    //省略get,set,空參,滿參
}

// 測試
public class Main {
    public static void main(String[] args) {
        ICourse course = new EnglishCourse("國小英語", 199D, "Mr.Zhang");
        System.out.println(
                "課程名字:"+course.getName() + " " +
                "課程價格:"+course.getPrice() + " " +
                "課程作者:"+course.getAuthor()
        );
    }
}
           

項目上線,但是出現了節假日打折的情況出現,現在有以下這幾種方法去修改:

1:修改接口(不可取,修改接口時其他的代碼也得改!)

2:修改實作類(不可取,這樣會有兩個擷取價格的方法!)

3:擴充實作方法(可取,不改變原有代碼,功能可實作!)

直接添加一個子類 SaleEnglishCourse ,重寫 getPrice()方法,這個方案對源代碼沒有影響,符合開閉原則,是以是可執行的方案

public class SaleEnglishCourse extends EnglishCourse {

    public SaleEnglishCourse(String name, Double price, String author) {
        super(name, price, author);
    }

    @Override
    public Double getPrice() {
        return super.getPrice() * 0.85;
    }
}
           

開閉原則的作用

開閉原則是面向對象程式設計的終極目标,它使軟體實體擁有一定的适應性和靈活性的同時具備穩定性和延續性。具體來說,其作用如下。

1. 對軟體測試的影響
軟體遵守開閉原則的話,軟體測試時隻需要對擴充的代碼進行測試就可以了,因為原有的測試代碼仍然能夠正常運作。
2. 可以提高代碼的可複用性
粒度越小,被複用的可能性就越大;在面向對象的程式設計中,根據原子和抽象程式設計可以提高代碼的可複用性。
3. 可以提高軟體的可維護性
遵守開閉原則的軟體,其穩定性高和延續性強,進而易于擴充和維護。
           

2、裡氏替換原則

原則定義:

繼承必須確定超類(被繼承的類稱為超類,繼承的類稱為子類)所擁有的性質在子類中仍然成立。

裡氏替換原則主要闡述了有關繼承的一些原則,也就是什麼時候應該使用繼承,什麼時候不應該使用繼承,以及其中蘊含的原理。裡氏替換原是繼承複用的基礎,它反映了基類與子類之間的關系,是對開閉原則的補充,是對實作抽象化的具體步驟的規範。
           

實作原理:

1、子類可以實作父類的抽象方法,但不能覆寫父類的非抽象方法
2、子類可以有自己的個性
3、覆寫或實作父類的方法時輸入參數可以被放大
4、覆寫或實作父類的方法時輸出結果可以被縮小
           

執行個體展示:

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

2、子類可以有自己的個性
在繼承父類屬性和方法的同時,每個子類也都可以有自己的個性,在父類的基礎上擴充自己的功能。前面其實已經提到,當功能擴充時,子類盡量不要重寫父類的方法,而是另寫一個方法,是以對上面的代碼加以更改,使其符合裡氏替換原則。
(略)

3、覆寫或實作父類的方法時輸入參數可以被放大
當子類的方法重載父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬松。
                                                (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);
    }
}
// 執行結果:
// parent execute say hello
// parent execute say hello
// 參數大小不一樣,一直用的都是string,父類的

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;
    }
}
           

裡式替換的作用:

裡氏替換原則是實作開閉原則的重要方式之一。
它克服了繼承中重寫父類造成的可複用性變差的缺點。
它是動作正确性的保證。即類的擴充不會給已有的系統引入新的錯誤,降低了代碼出錯的可能性。
           

3、依賴倒置原則

原則定義:

依賴倒置原則的原始定義為:高層子產品不應該依賴低層子產品,兩者都應該依賴其抽象;抽象不應該依賴細節,細節應該依賴抽象。其核心思想是:要面向接口程式設計,不要面向實作程式設計。

依賴倒置原則是實作開閉原則的重要途徑之一,它降低了客戶與實作子產品之間的耦合。

由于在軟體設計中,細節具有多變性,而抽象層則相對穩定,是以以抽象為基礎搭建起來的架構要比以細節為基礎搭建起來的架構要穩定得多。這裡的抽象指的是接口或者抽象類,而細節是指具體的實作類。

使用接口或者抽象類的目的是制定好規範和契約,而不去涉及任何具體的操作,把展現細節的任務交給它們的實作類去完成。
           

實作原理:

依賴倒置原則的目的是通過要面向接口的程式設計來降低類間的耦合性,是以我們在實際程式設計中隻要遵循以下4點,就能在項目中滿足這個規則。
每個類盡量提供接口或抽象類,或者兩者都具備。
變量的聲明類型盡量是接口或者是抽象類。
任何類都不應該從具體類派生。
使用繼承時盡量遵循裡氏替換原則。
           

執行個體展示:

public class T2 {
    public static void main(String[] args) {
        IDriver zhangsan = new Driver();
        Icar benz = new Benz();
        zhangsan.drive(benz);

        Icar bmw = new BMW();
        zhangsan.drive(bmw);
    }
}

interface IDriver {
    //    司機職責就是駕駛汽車
    public void drive(Icar car);
}

class Driver implements IDriver{
    //    司機職責就是駕駛汽車
    @Override
    public void drive(Icar car) {
        car.run();
    }
}

interface Icar {
    //    車的作用就是跑
    public void run();
}

class Benz implements Icar {
    //    車的作用就是跑
    @Override
    public void run() {
        System.out.println("奔馳車跑起來了");
    }
}

class BMW implements Icar {
    //    車的作用就是跑
    @Override
    public void run() {
        System.out.println("寶馬車跑起來了");
    }
}
           

依賴倒置原則作用:

依賴倒置原則的主要作用如下。
依賴倒置原則可以降低類間的耦合性。
依賴倒置原則可以提高系統的穩定性。
依賴倒置原則可以減少并行開發引起的風險。
依賴倒置原則可以提高代碼的可讀性和可維護性。
           

繼續閱讀