1. 程式人生 > >設計模式(0)—— 設計模式基石 —— 軟體設計七大原則

設計模式(0)—— 設計模式基石 —— 軟體設計七大原則

軟體設計七大原則。在面向物件軟體開發過程中,我們適當的遵循一定的設計原則,對於開發來說,能夠提高程式碼的健壯性,複用性,可維護性。同時利於團隊協作交流。

本文中的部分原則給出一個特定場景和具體程式碼實現。其它的設計原則貼出他人寫的博文地址。

1. 開閉原則(open close)

 定義: 軟體實體(類,模組,函式)應該對擴充套件開放,對修改關閉。
 解釋:用抽象構建框架,用實現擴充套件細節。
 優點:提高系統的可複用性,可維護性。
  • Code實現

    以書店的書籍作為一個簡單的例子

    書籍有3個簡單屬性:id,name,price。於是對於訪問書籍的資訊我們抽象 出一個介面類:

    public interface IBook {
    	Integer getId();
    	String  getName();
    	double  getPrice();
    }
    

    現在有關IT類的書。我們自然就可以實現這個介面:

    public class ITBook implements IBook{
    
        private Integer Id;
        private String name;
        private double price;
    
        @Override
        public Integer getId() {
            return this.Id;
        }
    @Override public String getName() { return this.name; } @Override public Double getPrice(){ return this.price; } }

    測試類:

    public class Test {
        public static void main(String[] args) {
            IBook cpp = new ITBook( 11, "CPP", 32.0);
            //print : id,name,price
            System.
    out.println( "Id: " + cpp.getId() + ", name: " + cpp.getName() + ", price:" + cpp.getPrice()); } }

    現正逢雙11,IT類的書打折(discount)。我們要對上面類的設計做出一些變化。
    為此我們可以嘗試著對IBook類修改,增加一個抽象函式:

    public interface IBook {
    	Integer getId();
    	String  getName();
    	double  getPrice();
    	//新增的程式碼
    	double  getDiscountPrice();
    }
    

    那麼我們的ITBook類必須要override這個函式。對於一個書店來說,肯定不止IT類的書,還有文學,藝術,工程等等不同類的書。這些類按照我們的規則來設計的話,一定也會implements IBook。如此一來,我們在IBook這個底層介面內修改程式碼實在不是一個好的建議,因為一旦我們修改了底層IBook類的程式碼,所有實現IBook介面的類都必須進行相應的修改。

    按照開閉原則:用抽象構建框架,用實現擴充套件細節
    在我們的例子中,我們用抽象搭建的框架就是IBook這個類,打折這個功能相當於是擴充套件。我們不應該修改用抽象封裝好的底層框架(對應於關閉);而是應該在凌駕於底層框架之上,進行功能的擴充套件(對應於開放)

    說起來也挺容易理解:上層的應用都建立在底層之上,如果底層的介面修改了,那麼所有依賴於這個這些介面的類都需要進行修改,否則會報錯。

    回到例子中。根據分析,我們不修改IBook這個底層介面類,而是修改上層的實體類,實現打折功能的擴充套件。

    新定義一個類ITDiscountBook繼承自ITBook:

    public class ITDiscountBook extends ITBook {
    
        private double discount;
    
        public ITDiscountBook(Integer id, String name, double price, double discount) {
            super(id, name, price);
            this.discount = discount;
        }
    
        public double getDiscountPrice(){
            return super.getPrice() * discount;
        }
    }
    

    測試類:

    public class Test {
        public static void main(String[] args) {
            //IBook cpp = new ITBook( 11, "CPP", 32.0);
            //System.out.println( "Id: " + cpp.getId() + ", name: " + cpp.getName() + ", price:" + cpp.getPrice() );
    
            IBook cppDiscountBook = new ITDiscountBook(11, "cpp", 32.0, 0.8);
            //注意 類的轉換
            System.out.println( "id:" + cppDiscountBook.getId() + ", name:" +  cppDiscountBook.getName() +
                                ", Original price: " + cppDiscountBook.getPrice() +
                                " Discount price: " + ((ITDiscountBook) cppDiscountBook).getDiscountPrice() );
        }
    }
    
  • 類關係圖(Diagram)
    開閉原則例項圖

2. 依賴倒置(dependency inversion)

定義:高層模組不應該依賴低層模組,二者都應該依賴其抽象
解釋:
	 抽象不應該依賴細節,細節應該依賴抽象
	 針對介面程式設計,不要針對實現程式設計
優點:減少類間的耦合性,提高系統穩定性,提高程式碼可讀性,可維護性。降低修改程式所造成的風險。
  • Code實現
    圍繞書店這個例子,我們定義一個顧客類(Customer),它能實現買(buy)的功能。顧客買不同型別的書籍能夠觸發不同的模組呼叫。假設現在書店裡面有三類書:IT,Art,Literature。Customer類和測試類Test實現如下:

    // 顧客類(低層模組):對於不同的行為定義不同的模組
    public class Customer {
    
       public void buyITBook(){
           System.out.println("Buy a IT Book.");
       }
    
       public void buyArtBook(){
           System.out.println("Buy a Art Book.");
       }
    
        public void buyLiteratureBook(){
            System.out.println("Buy a Literature Book.");
        }
    }
    //測試類(高層模組):測試類(高層)的功能依賴Customer(低層次)的具體實現
    public class Test {
        public static void main(String[] args) {
            Customer cus = new Customer();
            cus.buyITBook();
            cus.buyArtBook();
            cus.buyLiteratureBook();
        }
    }
    

    從上面的例子中可以看到,Customer類的設計使其擴充套件性很差,外部(Test)需要新增什麼功能,都需要重新在Customer類內部新增或者修改相應的程式碼。也就是說高層模組依賴低層模組

    現在我們嘗試修改程式碼,使其符合我們所說的依賴倒置原則。

    我們把這幾個Customer內部的功能模組抽離出來,使它們成為三個不同的類,然後Customer類只需要根據這三個類的統一介面觸發模組呼叫即可。如下面類關係圖所示:
    依賴倒置例類圖

    下面是抽離出來的程式碼:

    // 介面類,定義三個類的統一觸發行為方法
    public interface IBook {
        void buyBook();
    }
    

    /**********************三個實體類*************************/

    // ITBook類
    public class ITBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a IT Book.");
        }
    }
    
    // ArtBook 類
    public class ArtBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a Art Book.");
        }
    }
    
    // LiteratureBook 類
    public class LiteratureBook implements IBook {
        @Override
        public void buyBook() {
            System.out.println("Buy a Literature Book.");
        }
    }
    

    /*******************Customer類********************/

    public class Customer {
        public void buyABook(IBook iBook){
            iBook.buyBook();
        }
    }
    

    /****************測試Test類**************/

    public class Test {
        public static void main(String[] args) {
            Customer cus = new Customer();
            cus.buyABook( new ITBook() );
            cus.buyABook( new ArtBook() );
            cus.buyABook( new LiteratureBook() );
        }
    }
    

    可以看到程式碼的精彩之處在於,我們在外部(Test類)讓Customer類執行相應行為時,只需要傳入相應介面類就行,對Customer內部的實現絲毫沒有影響。Customer跟另外三個類的唯一的通訊渠道,就是通過一個介面類來作為函式引數,儘量降低耦合。

    同時該設計的擴充套件性是良好的,當我們需要新增新的行為時(在這裡就是買新的種類的書),我們只需要再建立一個類,使這個類實現IBook介面就行。

    另外,Customer的幾個實現版本總結如下:

    						/*1個原始版本不符合依賴倒置原則*/
    public class Customer {
       public void buyITBook(){
           System.out.println("Buy a IT Book.");
       }
       public void buyArtBook(){
           System.out.println("Buy a Art Book.");
       }
       public void buyLiteratureBook(){
           System.out.println("Buy a Literature Book.");
       }
    }
    						/*3個符合依賴倒置原則的設計方式*/
    // 介面傳參注入
    public class Customer {
        public void buyABook(IBook iBook){
            iBook.buyBook();
        }
    }
    // 構造器注入(單例)
    public class Customer {
    	private IBook iBook;
        public void Customer (IBook iBook){
            this.iBook = iBook;
        }
        public void buyABook(){
    		iBook.buyBook();
    	}
    }
    // setter注入
    public class Customer {
    	private IBook iBook;
        public void setIBook(IBook iBook){
            this.iBook = iBook;
        }
    }
    

3. 單一職責原則(single responsibility)

定義:不要存在多於一個導致類變更的原因
解釋:一個類/介面/方法只負責一項職責
優點:降低類的複雜度,提高類的可讀性,提高系統的可維護性,降低變更引起的風險

點選借鑑

4. 介面隔離原則(Interface isolation)

定義:
	用多個專門的介面,而不使用單一的總介面,客戶端不應該依賴它不需要的介面
解釋:
	一個類對一個類的以來應該建立在最小的介面上
	建立單一介面,不要建立龐大臃腫的介面
	儘量細化介面,介面中的方法儘量少
原則:
	注意適度原則,一定要適度
優點:
	符合高內聚,低耦合的設計思想,從而使得類具有很好的可讀性,可擴充套件性,可維護型。

點選借鑑

5. 迪米特原則(demeter)

定義
	一個物件應該對其它物件保持最小的瞭解,又叫最小知道原則
解釋
	儘量降低類之間的耦合
原則
	強調只和朋友交流,不和陌生人說話(朋友:出現在成員變數,方法的輸入,輸出引數中的類稱為朋友類。而出現在方法體內部的類不屬於朋友類。)
優點
	降低類間耦合

點選借鑑

6. 里氏替換原則(Liskov Substitution)

定義
	如果對每個型別為T1的物件O1,都有型別為T2的物件O2,使得以T1定義的所有程式P在所有的物件O1都替換成O2時,程式P的行為都沒有發生變化,那麼型別T2是型別T1的子型別
定義擴充套件
	一個軟體實體如果使用一個父類的話,那一定適用於其子類,所有引用父類的地方必須能透明地使用其子類的物件,子類物件能夠替換父類物件,而程式邏輯不變。
引申意義
	子類可以擴充套件父類的功能,但不能改變父類原有的功能
	含義
		子類可以實現父類的抽象方法,但不能覆蓋父類的非抽象方法。
		子類中可以增加自己特有的方法。
		當子類的方法過載父類的方法時,方法的前置條件(入參)要比父類方法的入參更寬鬆
		當子類的方法實現父類的方法時,方法的後置條件(返回值)要比父類更嚴格或相等。
優點
	約束繼承氾濫,開閉原則的一種體現
	加強程式的健壯性,同時變更時也可以做到非常好的相容性。提高程式的維護性,擴充套件性。降低需求變更時引入的風險

[點選借鑑](https://blog.csdn.net/Bitou_Von/article/details/4210654)

7. 組合複用原則(composition aggregation)

定義
	儘量使用物件組合或聚合,而不是繼承關係達到軟體複用的目的
解釋
	聚合
		has--A。電腦與U盤
	組合
		contains--A。國家:國與家
	繼承
		is--A。人與員工
優點
	使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其它類造成的影響相對較小

點選借鑑