其實,」設計模式「這個術語最先並不是出如今軟件設計中,而是被應用於建築領域的設計中。java
早在1977 年的時候,美國著名建築大師、加利福尼亞大學伯克利分校環境結構中心主任克里斯托夫·亞歷山大(Christopher Alexander)在他的著做《建築模式語言:城鎮、建築、構造(A Pattern Language: Towns Building Construction)中就提出了253種建築領域設計的基本模式。mysql
直到1987年,肯特·貝克(Kent Beck)和沃德·坎寧安(Ward Cunningham)率先將克里斯托夫·亞歷山大的這些模式思想應用在 Smalltalk 中的圖形用戶接口的生成中,遺憾的是並無引發軟件界的關注。(Smalltalk:被公認爲歷史上第二個面向對象的程序設計語言,有"面向對象編程之母"的稱號。)這裏的對象和接口就比如建築領域的牆壁和門窗。sql
3年後即1990年,軟件界纔開始研討關於設計模式的話題,相關會議也是層出不窮的召開。數據庫
1995 年,艾瑞克·伽馬(ErichGamma)、理査德·海爾姆(Richard Helm)、拉爾夫·約翰森(Ralph Johnson)、約翰·威利斯迪斯(John Vlissides)等 4 位做者合做出版了《設計模式:可複用面向對象軟件的基礎》(Design Patterns: Elements of Reusable Object-Oriented Software)一書,在本教程中收錄了 23 個設計模式,這是設計模式領域裏程碑的事件,致使了軟件設計模式的突破。這 4 位做者在軟件開發領域裏也以他們的「四人組」(Gang of Four,GoF)匿名著稱。編程
GoF(Gang of Four,四人組)所提出的設計模式主要基於如下的面向對象的設計原則:設計模式
對接口編程而不是對實現編程
優先使用對象組合而不是繼承架構
直到今天,狹義的設計模式仍是指GoF(Gang of Four,四人組)的這23種經典設計模式。oracle
軟件設計模式(Software Design Pattern),又稱設計模式。是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。歸納來說,它是解決特定問題的一系列套路,具備程語言無關性。ide
這世上本沒有什麼設計模式,用的人多了,也便成了設計模式。函數
學習設計模式的目的就是提升代碼的可重用性、可讀性、可靠性、靈活性及可維護性。天然也可
以提升程序猿的思惟能力、編程能力及設計能力。同時,學習並掌握設計模式是中級工程師進階高開或者架構師的必經之路。
總的來講,設計模式包含如下幾個基本要素:模式名稱、別名、動機、問題、解決方案、效果、結構、模式角色、合做關係、實現方法、適用性、已知應用、例程、模式拓展和相關模式等。
其實,設計模式的基本要素主要有如下四個,羅列以下:
設計模式提出之初就很明確地指定了設計模式的基本要素,可是實際多數軟件開發者會重點關注第1和第3點要素(即重點關注設計模式及其實現)而忽略第2和第4點要素(即忽略設計模式的產生背景及設計目標),最終致使設計出的應用編碼邏輯繁雜或得不到預期的效果。
下面,咱們用一張圖清晰地記錄下設計模式中的幾大原則(部分文獻中記錄的爲六大原則,就是沒有合成複用原則):
開閉原則是設計模式中的總原則,開閉原則就是說:對拓展開放、對修改關閉。模塊應該在儘可能不修改代碼的前提下進行拓展,這就須要使用接口和抽象類來實現預期效果。
舉個栗子,咱們以書店銷售書籍爲例,類圖以下:
public interface IBook { public String getName(); public int getPrice(); public String getAuthor();
}
小說類NovelBook實現了IBook接口,NovelBook類具體代碼以下:
public class NovelBook implements IBook{ // 書籍名稱 private String name; // 書籍價格 private int price; // 書籍做者 private String author; public NovelBook(String _name, int _price, String _author) { this.name = _name; this.price = _price; this.author = _author; } @Override public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } @Override public String getAuthor() { return author; } public void setAuthor(String author) { this.author = author; }
}
而後咱們打印下書籍價格等信息:
public class Main { public static void main(String[] args) { IBook novel = new NovelBook("笑傲江湖", 45, "金庸"); System.out.println("書籍名字:" + novel.getName() + "\n書籍做者:" + novel.getAuthor() + "\n書籍價格:" + novel.getPrice()); }
}
具體輸出以下:
書籍名字:笑傲江湖
書籍做者:金庸
書籍價格:45
那麼問題來了,假若某一天市場環境很差,書籍須要打折銷售的話(好比40元以上的書籍按9折銷售,其它的8折銷售),以上代碼如何應對這種需求變化呢?通常來講,有以下三種方法來解決這個問題:
OffNovelBook類的代碼示例以下:
public class OffNovelBook extends NovelBook{ public OffNovelBook(String _name, int _price, String _author) { super(_name, _price, _author); } @Override public int getPrice() { // 原價 int selfPrice = super.getPrice(); // 折扣價 int offPrice = 0; if (selfPrice >= 40) { offPrice = selfPrice * 90 / 100; } else { offPrice = selfPrice * 80 / 100; } return offPrice; }
}
同時,main中調用相應調整以下:
public class Main { public static void main(String[] args) { IBook novel = new NovelBook("笑傲江湖", 45, "金庸"); System.out.println("書籍名字:" + novel.getName() + "\n書籍做者:" + novel.getAuthor() + "\n書籍價格:" + novel.getPrice()); System.out.println("------------------------"); IBook novel2 = new OffNovelBook("笑傲江湖", 45, "金庸"); System.out.println("書籍名字:" + novel2.getName() + "\n書籍做者:" + novel2.getAuthor() + "\n書籍折扣後價格:" + novel2.getPrice()); }
}
輸出的書籍價格信息以下:
書籍名字:笑傲江湖
書籍做者:金庸
書籍價格:45
------------------------
書籍名字:笑傲江湖
書籍做者:金庸
書籍折扣後價格:40
何爲單一指責原則,指的是一個類或者模塊有且只有一個改變的緣由。若是模塊或類承擔的指責過多,就等於這些指責耦合在一塊兒,這樣一個模塊的變快可能會削弱或抑制其它模塊的能力,這樣的耦合是十分脆弱地。因此應該儘可能保持單一職責原則,此原則的核心就是解耦和加強內聚性。
舉個栗子,以下需求涉及到用戶的相關模塊操做,因此定義了IUserInfo接口,以下:
IUserInfo userInfo = new UserInfo(); // 操做BO時 IUserBO userBO = (IUserBO) userInfo; userBO.setPassword("123456"); // 行爲操做時 IUserBiz userBiz = (IUserBiz) userInfo; userBiz.deleteUser();
單一職責原則,再舉個栗子就是萬能類:即在一個類(或方法)中徹底能夠實現系統全部功能。可是這樣作每每模塊嚴重耦合,牽一髮而動全身。因此須要職責分離,MVC架構中該原則體現的就比較明顯。
在如今流行的微服務架構體系中,最頭疼的就是服務拆分,拆分的粒度也頗有講究,標準的應該是聽從單一原則,避免服務拆分時發生各類撕逼行爲:」本應該在A服務中的被安排在了B服務中「,因此服務的職責劃分尤其重要。
再有就是,作service層開發時,早期的開發人員會將數據庫操做放在service中,好比getConnection,而後執行prepareStatement,再就是service邏輯處理等等。但是後來發現數據庫要由原來的mysql變動爲oracle,service層代碼豈不是須要重寫一遍,天了嚕...直接崩潰跑路。
」我單純,因此我快樂「用來形容單一指責原則再恰當不過了。
里氏替換原則的解釋是,全部引用基類的地方必須能透明地使用其子類的對象。通俗來說的話,就是說,只要父類能出現的地方子類就能夠出現,而且使用子類替換掉父類的話,不會產生任何異常或錯誤,使用者可能根本就不須要知道是父類仍是子類。反過來就不行了,有子類的地方不必定能使用父類替換。
好比某個方法接受一個Map型參數,那麼它必定能夠接受HashMap、LinkedHashMap等參數,可是反過來的話,一個接受HashMap的方法不必定能接受全部Map類型參數。
里氏替換原則是開閉原則的實現基礎,它告訴咱們設計程序的時候儘量使用基類進行對象的定義及引用,具體運行時再決定基類對應的具體子類型。
接下來舉個栗子,咱們定義一個抽象類AbstractAnimal對象,該對象聲明內部方法」跳舞「,其中,Rabbit、Dog、Lion分別繼承該對象,另外聲明一個Person類,該類負責餵養各類動物,Client類負責邏輯調用,類圖以下:
其中,Person類代碼以下:
public class Person { private AbstractAnimal animal; public void feenAnimal(AbstractAnimal _animal) { this.animal = _animal; } public void walkAnimal(){ System.out.println("人開始溜動物..."); animal.dance(); }
}
main函數調用的時候以下:
public class Main { public static void main(String[] args) { Person person = new Person(); person.feenAnimal(new Rabbit()); person.walkAnimal(); }
}
打印輸出:
人開始溜動物...
小白兔跳舞...
依賴倒置原則(Dependency Inversion Principle,DIP)的定義:程序要依賴於抽象接口,不要依賴於具體實現。簡單的說就是要求對抽象進行編程,不要對實現進行編程,這樣就下降了客戶與實現模塊間的耦合。
依賴倒置原則要求咱們在程序代碼中傳遞參數時或在關聯關係中,儘可能引用層次高的抽象層類,即便用接口和抽象類進行變量類型聲明、參數類型聲明、方法返回類型聲明,以及數據類型的轉換等,而不要用具體類來作這些事情。
依賴倒置原則,高層模塊不該該依賴低層模塊,都應該依賴抽象。抽象不該該依賴細節,細節應該依賴抽象。其核心思想是:要面向接口編程,不要面向實現編程。
舉個栗子,拿顧客商店購物來講,定義顧客類以下,包含一個shopping方法:
public class Customer { public void shopping (YanTaShop shop) { System.out.println(shop.sell()); }
}
以上表示顧客在"雁塔店"進行購物,假如再加入一個新的店鋪"高新店",表示修改以下:
public class Customer { public void shopping (GaoXinShop shop) { System.out.println(shop.sell()); }
}
這顯然是設計不合理的,違背了開閉原則。同時,顧客類的設計和店鋪類綁定了,違背了依賴倒置原則。解決辦法很簡單,將Shop抽象爲具體接口,shopping入參使用接口形式,顧客類面向接口編程,以下:
public class Customer { public void shopping (Shop shop) { System.out.println(shop.sell()); } } interface Shop{ String sell();
}
類圖關係以下:
接口隔離原則(Interface Segregation Principle,ISP)的定義是客戶端不該該依賴它不須要的接口,類間的依賴關係應該創建在最小的接口上。簡單來講就是創建單一的接口,不要創建臃腫龐大的接口。也就是接口儘可能細化,同時接口中的方法儘可能少,保持接口純潔性。
咱們所講的接口主要分爲兩大類,一是實例接口,好比使用new關鍵字產生一種實例,被new的類就是實例類的接口。從這個角度出發的話,java中的類其實也是一種接口。二是類接口,java中經常使用interface關鍵字定義。
舉個栗子來講,咱們使用接口IPrettyGirl來描述美女,剛開始類圖可能描述以下:
可是發現該接口中包含對美女的外觀描述、內在美描述等,幾乎將美女的全部特性所有歸入,這顯然不是一個很好的設計規範,好比在唐朝,在那個以福爲美的時代對美的理解就不一樣,就會出現單純goodLooking過關就是美女的結果,因此這裏咱們須要將接口隔離拆分。將一個接口拓展爲兩個,增長系統靈活性及可維護性。
這裏咱們將美女接口拆分爲內在美、外在美兩個接口,系統靈活性提升了,另外接口間還能使用繼承實現聚合,系統拓展性也獲得了加強。
迪米特法則(Law of Demeter,LOD),有時候也叫作最少知識原則(Least Knowledge Principle,LKP),它的定義是:一個軟件實體應當儘量少地與其餘實體發生相互做用。迪米特法則的初衷在於下降類之間的耦合。
舉個栗子,拿教師點名來說,體育老師須要清點班上學生人數,教師通常不是本身親自去數,而是委託組長或班長等人去清點,即教師經過下達命令至班長要求清點人數:
public class Girl { } public class GroupLeader { private final List<Girl> girls; public GroupLeader(List<Girl> girls) { this.girls = girls; } public void countGirls() { System.out.println("The sum of girls is " + girls.size()); } } public class Teacher { public void command(GroupLeader leader){ leader.countGirls(); } } public class Main { public static void main(String[] args) throws Exception { Teacher teacher = new Teacher(); GroupLeader groupLeader = new GroupLeader(Arrays.asList(new Girl(), new Girl())); teacher.command(groupLeader); }
}
上述例子中,若是去掉GroupLeader這個中間人角色,教師就會直接去清點人數,這樣作會違反迪米特法則。
合成複用原則是經過將已有的對象歸入新對象中,做爲新對象的成員對象來實現的,新對象能夠調用已有對象的功能,從而達到複用。原則是儘可能首先使用合成/聚合的方式,而不是使用繼承。