設計模式(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; }
測試類:
public class Test { public static void main(String[] args) { IBook cpp = new ITBook( 11, "CPP", 32.0); //print : id,name,price System.
現正逢雙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。人與員工
優點
使系統更加靈活,降低類與類之間的耦合度,一個類的變化對其它類造成的影響相對較小