1. 程式人生 > 其它 >設計模式的六大原則

設計模式的六大原則

一.設計模式

設計模式,即Design Patterns,是指在軟體設計中,被反覆使用的一種程式碼設計經驗。使用設計模式的目的是為了可重用程式碼,提高程式碼的可擴充套件性和可維護性。

設計模式這個術語是上個世紀90年代由Erich Gamma、Richard Helm、Raplh Johnson和Jonhn Vlissides四個人總結提煉出來的,並且寫了一本Design Patterns的書。這四人也被稱為四人幫(GoF)。

GoF把23個常用模式分為建立型模式、結構型模式和行為型模式三類,後面會一一說明。

二.六大原則

為什麼要使用設計模式?根本原因還是軟體開發要實現可維護、可擴充套件,就必須儘量複用程式碼,並且降低程式碼的耦合度。設計模式主要是基於OOP程式設計提煉的,它基於以下幾個原則:

  • 單一職責原則 (SRP):

一個類所承擔的功能或職責必須是同一類或者相近的,不要讓一個類承擔過多的職責。換一句話說,一個類的職責應該單一。(介面的職責必須單一。類的設計儘量做到只有一個原因引起類的變化,指的是設計類而不是實現類,實現類做到單一職責的話無形中會增加很多類,會使原本簡單的事情變得更加複雜。)

  • 開閉原則 (OCP):

一個軟體實體應該對擴充套件開發,對修改關閉。其含義是說一個軟體實體應該通過擴充套件實現變化,而不是通過修改內部已有的程式碼來實現變化。

軟體實體是啥?

  • 專案或軟體產品中按照一定邏輯劃分的模組
  • 抽象和類
  • 方法

開閉原則指導我們,當軟體需要變化時應”儘量通過”擴充套件的方式來修改,而不是通過變化已有程式碼來實現,這裡說的是儘量,並不是絕對不可以修改原始類,當我們嗅到“腐化”氣味時應儘量早重構。而不是通過繼承等方式新增新的實現,這會導致類的膨脹及歷史程式碼的遺留。

通常我們可以用介面或抽象類來約束一組可變化的行為。主要包含3個層次:

  1. :通過介面或抽象類約束擴充套件,對擴充套件邊界定義,不允許實現類出現介面或抽象類以外的public 方法。
  2. :引數型別、引用變數儘量使用介面或抽象對類,而不是實現類。
  3. :抽象層儘量保持穩定。
    當然在實際應用過程中,往往修改原始碼和擴充套件是同時存在的。
  • 里氏替換原則 (LSP)

所有引用基類的地方必須能透明地使用其子類物件。

通俗一點解釋是這樣的:只要任何有父類出現的地方(如形參),都可以替換為子類,而且替換為子類也不會產生異常和錯誤。對於使用者(方法)本身不需要關心到底是父類還是子類。(但是,有子類出現的地方,替換為父類就不一定可以了。

)
一個例子:

//視窗類
public class Window(){
    public void show(View child){
        child.draw();
    }
}
public abstract class View(){
    public abstract void draw();
    public void measure(int widht,int height){
        //測量檢視大小
    }
}
public class Button extends View{
    public void draw(){
        //繪製按鈕
    }
}

public class TextView extends View{
    public void draw(){
        //繪製文字
    }
}

里氏替換原則為良好的繼承定義了一個規範:

1、子類必須完全實現父類的方法。我們在做系統設計的時候經常定義一個介面或抽象類,然後編碼實現,呼叫類直接傳入介面或抽象類,其實這裡已經使用了里氏原則。

如果子類不能全實現父類的方法,或者父類的某些方法在子類中已經發生畸變,建議斷開繼承關係,採用依賴、聚合、組合等關係代替。

2、子類有自己的個性。即有子類出現的地方,父類未必可以。

3、覆蓋或實子類過載父類的方法時,傳入的引數必須比父類更寬鬆(相同或範圍大)。(否則會出現本來虛呼叫父類方法的地方呼叫了子類的方法)

4、覆蓋或實子類過載父類的方法時,返回的結果必須範圍更小(和父類型別相同或是父類返回型別的子類)。

  • 迪米特原則 (LOD)

一個物件應該對一個被呼叫的類(被耦合)有最少的瞭解。呼叫者只需要知道它需要呼叫的方法即可。類與類之間的關係越密切,當一個類發生改變時,對另一個類影響也越大。

迪米特原則包含的含義:

  • 只和朋友交流:每個類必然會和其他類有耦合關係,兩個物件間的耦合就成為朋友關係(聚合、組合、依賴等)。朋友類得定義是這樣的:出現在成員變數、方法的輸入、輸出引數中的稱為成員朋友類,而出現在方法體內部的類不屬於朋友類。
  • 朋友之間也是有距離的:不要對外公佈太多的public方法和非靜態的public變數,儘量內斂。

迪米特原則的核心觀念是:解耦。即:高內聚、低耦合。

  • 介面隔離原則 (ISP)

關於介面有一點要說明:類也是介面

有兩種定義方法:一種為類不應該依賴它不需要的介面;另一種為類之間的依賴關係應該建立在最小的介面上。介面隔離的原則是將非常龐大、臃腫的介面分割成更小更具體的介面。

有的時候會感覺單一職責和介面隔離隔離原則很像,單一職責更關注的是功能的單一,是業務層次上的劃分。而介面隔離原則更關心的是介面的數量要少。

比如說一個介面有一組10個功能,提供給若干個模組使用,每個模組按照按照不同的許可權只能使用介面中的一部分功能。按照單一職責原則是允許的,但是按照介面隔離原則是不允許的,介面隔離原則要求:”儘量使用多個專用介面”,意思為有幾個模組就提供幾個介面,而不是建立一個龐大的介面供所有模組使用。

->但是介面設計時有限度的,當然也不要分了隔離介面而將全部介面都細化,這樣就變為過度設計了

最佳方法:

1. 一個介面只服務於一個模組或業務流程
2.  壓縮介面對外提供的public方法
  • 依賴倒置原則 (DIP)

依賴倒置主要是實現解耦,使得高層次的模組不依賴於低層次模組的具體實現細節。
幾個關鍵點:
a.高層模組不應該依賴底層模組(具體實現),二者都應該依賴其抽象(抽象類或介面)。模組之間的依賴通過抽象產生,實現類之間不發生直接依賴關係,依賴關係通過介面或抽象類產生。(高層模組就是呼叫端,底層模組就是具體實現類.)
b.抽象不應該依賴細節
c.細節應該依賴於抽象,用java語言解釋就是:就是各個模組之間相互傳遞的引數宣告為抽象型別,而不是宣告為具體的實現類;

->依賴導致的本質是通過抽象使各個類或者模組的實現彼此獨立,不相互影響,實現模組間的鬆耦合。每個類(底層模組)儘量有介面和抽象類,或者介面抽象類二者皆有。變數的宣告儘量是抽象或者介面,儘量不覆蓋父類的方法

示例:母親給孩子講故事,只要給她一本書,她就可以照著書給孩子講故事了。程式碼如下:

//視窗類
class Book{  
    public String getContent(){  
        return "很久很久以前有一個阿拉伯的故事……";  
    }  
}  

class Mother{  
    public void narrate(Book book){  
        System.out.println("媽媽開始講故事");  
        System.out.println(book.getContent());  
    }  
}  

public class Client{  
    public static void main(String[] args){  
        Mother mother = new Mother();  
        mother.narrate(new Book());  
    }  
}  

執行結果:媽媽開始講故事 很久很久以前有一個阿拉伯的故事……

執行良好,假如有一天,需求變成這樣:不是給書而是給一份報紙,讓這位母親講一下報紙上的故事,報紙的程式碼如下:

class Newspaper{  
    public String getContent(){  
        return "林書豪38+7領導尼克斯擊敗湖人……";  
    }  
}  

這位母親卻辦不到,因為她居然不會讀報紙上的故事,這太荒唐了,只是將書換成報紙,居然必須要修改Mother才能讀。假如以後需求換成雜誌呢?換成網頁呢?還要不斷地修改Mother,這顯然不是好的設計。原因就是Mother與Book之間的耦合性太高了,必須降低他們之間的耦合度才行。
所以我們引入一個抽象的介面IReader.(讀物,帶字的東西都可以屬於讀物).

interface IReader{  
    public String getContent();  
}  

Mother類與介面IReader發生依賴關係,而Book和Newspaper都屬於讀物的範疇,他們各自都去實現IReader介面,這樣就符合依賴倒置原則了,程式碼修改為:

class Newspaper implements IReader {  
    public String getContent(){  
        return "林書豪17+9助尼克斯擊敗老鷹……";  
    }  
}  

class Book implements IReader{  
    public String getContent(){  
        return "很久很久以前有一個阿拉伯的故事……";  
    }  
}  

class Mother{  
    public void narrate(IReader reader){  
        System.out.println("媽媽開始講故事");  
        System.out.println(reader.getContent());  
    }  
}  

public class Client{  
    public static void main(String[] args){  
        Mother mother = new Mother();  
        mother.narrate(new Book());  
        mother.narrate(new Newspaper());  
    }  
}  

這樣修改後,無論以後怎樣擴充套件Client類,都不需要再修改Mother類了。這只是一個簡單的例子,實際情況中,代表高層模組的Mother類將負責完成主要的業務邏輯,一旦需要對它進行修改,引入錯誤的風險極大。所以遵循依賴倒置原則可以降低類之間的耦合性,提高系統的穩定性,降低修改程式造成的風險。

依賴倒置原則在Java中的表現就是:模組間通過抽象發生,實現類之間不發生直接依賴關係,其依賴關係是通過介面或者抽象類產生的。如果類與類直接依賴細節,那麼就會直接耦合,那麼當修改時,就會同時修改依賴者程式碼,這樣限制了可擴充套件性。

最後,我們可以發現將6大原則的首字母組合起來,就是SOLID(穩定的),不是說按照一定的設計模式來設計程式就能應對各種場景,或不對程式碼結構做任何修改。只是我們在進行程式設計的時候儘量遵循這6大原則。