原文連結:
http://47.93.55.72/2018/11/13/%E8%BF%98%E5%9C%A8%E6%84%81%E4%B8%8D%E6%87%82%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E5%90%97%EF%BC%9F%E7%9C%8B%E8%BF%87%E6%9D%A5/
設計模式的作用
工欲善其事,必先利其器.若想寫好代碼,在千萬行代碼中你的系統依舊結構邏輯清晰,高度可拓展,高度的可用和健壯,你可能就需要好好學習設計模式了,其次你是否還會因為看各種源碼而頭疼,看不懂,太複雜,理不斷,剪還亂.學習了設計模式,我們可能對各種架構源碼的設計思想會有一些全新的認識.正所謂知其然,知其是以然然,知其所必然.總而言之,設計模式是做好程式員的必經之路.
什麼是設計模式
一句話概括,設計模式是思想,針對各種複雜的業務場景而總結的一種規範和完美的業務功能的實作.(通俗的說就是前人針對各種複雜業務場景實作的方式和思想)
為什麼要用設計模式
仁者見仁智者見智,我談談我為什麼用設計模式,我曾經在創業公司單獨開發過一款APP的背景,在寫了半年之後,我完成了很多功能,APP的開發是不确定性的,需求時常改動,後來的的代碼我自己看不懂了,改的太亂了,各種功能的拓展,相容,當時就是随性而寫,實作功能便好,後來才發現代碼結構設計的重要性,之後來到某500強企業,看到大型的大資料管理平台,更加證明了我的這一點,大型系統的建構是必須遵照設計模式去寫的,不然對于一個G的代碼維護誰能理得清晰呢?
學好設計模式的前提
學好設計模式就必須先了解一些些代碼的設計規則
- 開閉原則(Open Close Principle)
開閉原則就是說對擴充開放,對修改關閉。在程式需要進行拓展的時候,不能去修改原有的代碼,實作
一個熱插拔的效果。是以一句話概括就是:為了使程式的擴充性好,易于維護和更新。想要達到這樣的
效果,我們需要使用接口和抽象類.
- 裡氏代換原則(Liskov Substitution Principle)
裡氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。裡氏代換原
則中說,任何基類可以出現的地方,子類一定可以出現。LSP 是繼承複用的基石,隻有當衍生類可以
替換掉基類,軟體機關的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增
加新的行為。裡氏代換原則是對“開-閉”原則的補充。實作“開-閉”原則的關鍵步驟就是抽象化。而
基類與子類的繼承關系就是抽象化的具體實作,是以裡氏代換原則是對實作抽象化的具體步驟的規範。
- 依賴倒轉原則(Dependence Inversion Principle)
這個是開閉原則的基礎,具體内容:針對接口程式設計,依賴于抽象而不依賴于具體。
- 接口隔離原則(Interface Segregation Principle)
這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,
從這兒我們看出,其實設計模式就是一個軟體的設計思想,從大型軟體架構出發,為了更新和維護友善。
是以上文中多次出現:降低依賴,降低耦合。
- 迪米特法則(最少知道原則)(Demeter Principle)為什麼叫最少知道原則,就是說:一個實體應當盡量少的與其他實體之間發生互相作用,使得系統功能
子產品相對獨立。
- 合成複用原則(Composite Reuse Principle)
原則是盡量使用合成/聚合的方式,而不是使用繼承。
常用的設計模式
- 建立型模式,共五種:工廠方法模式、抽象工廠模式、單例模式、建造者模式、原型模式。
- 結構型模式,共七種:擴充卡模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。
- 行為型模式,共十一種:政策模式、模闆方法模式、觀察者模式、疊代子模式、責任鍊模式、指令模式、備忘錄模式、狀态模式、通路者模式、中介者模式、解釋器模式。
## 詳解特别常用的設計模式
每個設計模式都不能去死記硬背,尋找一些場景去了解學習。接下來也會使用源碼和部落格講解結合,尋找一些業務場景去講解每種設計模式的設計思想.
==源碼的位址詳見==:https://github.com/17302102404/deisgnPattern
### 工廠模式
- 核心思想和業務目的
生成一個我需要的執行個體,我不需要知道生産的過程。我隻要按你的要求你給我一個執行個體就好。這個設計的合理和細節的處理在于設計。
- 實作方式
1. 簡單工廠模式 (模式簡單,可拓展性較低,不可配置,植入)
```
Class MilkFactory //牛奶工廠
get milk(String name){
if(name.equals("yinli")){
new Yinli();
}else if("mengniu"){
new Mengniu();
}..........
}
```
2. 抽象工廠模式(可拓展,使用者體驗好,提供選擇,所有的結果右選擇決定,使用者不要需要輸入,送出代碼的健壯性,抽象類的抽象方法,統一相同特性,易于管理)
```
abstract Class AbstractFactory{
commons()//公共需要的方法
}
第二層:牛奶工廠
abstract Class MilkFactory() extends AbstractFactory{
milkcomons() //牛奶共同特征方法
abstract fun()//抽象方法
getYinli(){
new YinliFactory();
}
getMengniu(){
mew MengniuFactory'();
}
...............
}
第三層:品牌牛奶工廠
class MilkFactory extends MilkFactory
```
- 抽象工廠相對簡單工廠的優勢:
以這種層次結構清晰,将一個複雜的流程結構化,階層化,代碼的維護性和健壯性得到提升。最重要的是利于拓展.工廠模式時創造模式:解決使用者和産品之間的關系,對于使用者并不知道有哪些産品,但我們可提供選擇,例如MilkFactory中的get..... 方法即可知道有哪些品牌的牛奶,相對于簡單工廠不用輸入參數,第二可拓展性,新增一個品牌牛奶,隻有他們建立一個工廠,然後在我們這注冊一下,使用者就可以get..
,方式對比之前未發生改變,這就是抽象的工廠模式.Spring的Beanfactory就是使用的抽象工廠模式。
### 單例模式
- 核心思想和業務目的
單例模式通常的說就是系統在整個運作過程中一個類隻允許有一個執行個體(整個過程隻new了一次)
- 業務實作的難題
在多線程并發情況下往往需要一些對象是單例的,如何保證多線程情況下的單例呢?
比如:某個系統的環境配置,環境變量類,這些隻存在一份的都應該是單例的,在系統運作時候就一直應該保證唯一,在系統運作過程中,每個線程拿到的都是一樣的執行個體。
- 實作方式(餓漢式,懶漢式,注冊登記式,枚舉式)
1. 餓漢式:在執行個體使用之前,不管你用不用,我都先new出來再說,避免了線程安全問題,對象加載時已建立靜态的對象:
```
Class Hungry {
private Static Hungry hungry = new Hungry();
public Static Hungry getInstance(){
return hungry ;
}
}
```
2. 懶漢式:預設加載時候不執行個體化,在使用的使用才進行執行個體化(延時加載)
- 存線上程程安全懶漢式
```
Class Lazy {
private Static Lazy lazy = null ;
public Static Lazy getInstance(){
if(lazy ==null){
lazy = new hungry();
}
return lazy;
}
}
```
- 線程安全,但性能受限的做法
在上述的方法中加上synchronized 同步鎖即可
```
Class Lazy {
private Static Lazy lazy = null ;
public synchronized Static Lazy getInstance(){
if(lazy ==null){
lazy = new hungry();
}
return lazy;
}
}
```
- 以上兩種在實際應用場景中均不建議使用,如果要用懶漢式,建議使用雙重鎖
觀察可知,但對于第二種做法可以将鎖的粒度減小,針對多線程,并非多需要加鎖,我們需要單例,隻需要針對hungry對象為null的線程并非的時候才需要加鎖,故優化的寫法可以寫為
```
Class Lazy {
private Static Lazy lazy = null ;
public Static Lazy getInstance(){
if(lazy ==null){ //針對lazy==null才需要同步
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new hungry();
}
return lazy;
}
```
3. 注冊登記式:每使用一次,都往一個固定的容器中國去注冊并且使用過的對象進行緩存,下次去取對象的時候,就直接從緩存中取值,以保證每次擷取同一個對象。Ioc中的單例模式,就是典型的注冊式單例。用map将對象存起來
```
private Object readResolve(){
private static ConcurrentHashMap<String,Object> map = new ConcurrentHashMap();
public static Object getInstance(String name){
if(name == null){
return new RegisterPattern();
}
if(map.get(name) == null) {
map.put("name", new RegisterObject());
}
return map.get("name");
}
}
```
### 原型模式:
- 核心思想和業務目的
原型模式就是實作兩個實體模型之間的複制
- 開發場景
spring中的原型模式:scope=“prototype”, 預設是單例模式,也可以是原型模式
業務模型直接的互相轉化
1. VO(View Object):視圖對象,用于展示層,它的作用是把某個指定頁面(或元件)的所有資料封裝起來。
2. DTO(Data Transfer Object):資料傳輸對象,這個概念來源于J2EE的設計模式,原來的目的是為了EJB的分布式應用提供粗粒度的資料實體,以減少分布式調用的次數,進而提高分布式調用的性能和降低網絡負載,但在這裡,我泛指用于展示層與服務層之間的資料傳輸對象。
3. DO(Domain Object):領域對象,就是從現實世界中抽象出來的有形或無形的業務實體。
4. PO(Persistent Object):持久化對象,它跟持久層(通常是關系型資料庫)的資料結構形成一一對應的映射關系,如果持久層是關系型資料庫,那麼,資料表中的每個字段(或若幹個)就對應PO的一個(或若幹個)屬性。
- 實作方式
apache 反射實作
clone() jdk提供的方法
- 注意事項
淺複制和深複制的差別:
- 淺複制:隻是最外層的對象不是同一個記憶體位址,屬性對象都同一個記憶體空間(一般這種複制是不符合業務場景的,一般要求兩個對象的完全獨立和隔離),其中jdk的clone()預設的是淺複制
- 深度複制:深度複制是複制之後,兩個對像的屬性對象也完全隔離和獨立。可以通過對象序列化和反序列化去實作
### 代理模式
- 核心思想和業務目的
代理模式的思想是有一個對象不會無法做某事,需要另一個對象來代理他做事,是以代理模式一定存在連個對象,代理角色,被代理的角色(目标對象),由被代理角色來做最終的決定,代理角色通常持有被代理角色的引用。最終是目标對象能夠完成某個過程。
- 開發場景
AOP典型的實作,中介,黃牛,媒婆,攔截器,專人做專事,自己不想做或者不會做的事給别人來做,增強
- 實作方式
靜态代理和動态代理,代理的實作過程,一定有一個代理類和被代理類,動态動态代理一般生産的是代碼執行過程中動态的生成一個代理類,本質和靜态代理一樣,但可拓展确比靜态代理提升了太多,我們可以在執行過程中将動态代碼類輸出檔案,反編譯便可看出動态代理的過程。
參考github上的代碼檢視jdk和cglib實作動态代理過程:https://github.com/17302102404/deisgnPattern/tree/master/src/main/java/org/czx/proxypattern
- 動态代理和靜态的代理的差別
最大的過程是動态代理是執行過程才會生成代理對象,執行過程會通過代碼的重組,編譯,jvm加載産生一個代理對象,執行完之後就銷毀了。而靜态代理是未執行之前就定義好的。動态代理統一代理方法管理,沒必要給每個需要代理的對象建立一個代理對象,提高可拓展性。
### 政策模式
- 核心思想和業務目的
給定一些固定的東西去做選擇,使用者的選擇處理一個固定的算法的時候需要用到政策模式,典型的應用場景就是電商的購物支付的流程
- 開發場景
訂單支付場景,支付方式很多(微信,支付寶,餘額,銀行卡等)
支付的方式裡面的流程都是固定流程,需單獨拆分,對于使用者就是下單,選擇支付。
訂單支付需要做一個政策。
- 實作方式
訂單支付流程代碼設計詳見:https://github.com/17302102404/deisgnPattern/tree/master/src/main/java/org/czx/strategypattern
### 模闆方法模式
- 核心思想和業務目的
對于一些具體的業務流程,必須安裝某個流程去執行,但是中間的某個過程可以進行自定義。如spring源碼中的jdbctemplate,其中一些代碼設計實作了解耦,dao層如何不用繼承而是定義屬性就能實作。
- 實作方式
對于一個抽象的方法引入一個接口,要想使用某個方法你可以自定義接口的實作類,進而實作了可以自定義某個模闆流程的方法。
具體可以檢視JDBCTemplate中的模闆類,其中RowMapper就是實作查詢自定義結果的接口。
### 觀察者模式
- 核心思想和業務目的
觀察者模式本質其實就是觀察者和被觀察者之間能互動,就是被觀察者某個狀态發生變化,觀察者去觸發某個動作,典型的業務場景就是釋出訂閱,zookeeper的監控,監聽器、日志收集、短信通知、郵件通知
- 實作方式
對象的設計
1. Subject(被觀察的對象接口):規定ConcreteSubject的統一接口 ; 每個Subject可以有多個Observer
1. ConcreteSubject(具體被觀察對象):維護對所有具體觀察者的引用的清單 ;–狀态發生變化時會發送通知給所有注冊的觀察者
1. Observer(觀察者接口):規定ConcreteObserver的統一接口;定義了一個update()方法,在被觀察對象狀态改變時會被調用
1. ConcreteObserver(具體觀察者):維護一個對ConcreteSubject的引用;特定狀态與ConcreteSubject同步; 實作Observer接口,update()方法的作用:一旦檢測到Subject有變動,就更新資訊
具體代碼詳見:https://github.com/17302102404/deisgnPattern/tree/master/src/main/java/org/czx/observerpattern
### 擴充卡模式和裝飾器模式
- 說明
這兩種模式差不多,其中裝飾器模式實際上一種特殊的擴充卡模式,所謂的擴充卡模式,擴充卡其實就是一個轉換,在很多場景中我們寫代碼需要相容一些舊的功能,
我們就可以使用擴充卡模式可以做一些業務上的轉換。
可以說擴充卡其實是一種思想,轉換的邏輯也要因具體業務而定。裝飾器模式就是為了某個實作類在不修改原始類的基礎上進行動态的覆寫或者增加方法。
但是的保持跟原有類的層級關系,比如流的那塊進階流其實就是對低級流的增強。
- 實作方式
實作擴充卡模式的過程無非也是繼承和重組
實作裝飾器的過程就是将舊的作為一個成員變量
```
class A{
public void run(){
..............
}
public void go(){
}
}
//B類擁有A所有的功能,并且B對A進行的拓展
class B{
private A a;
public B(A a){
this.a=a;
}
public void run(){
this.a.run();
}
public void go(){
this.a.go();
}
//public void quickrun(){
...............
}
}
```
### 委派模式
委派模式不在23種設計模式中,但卻是寫代碼的一種很好的設計模式
- 設計思想和應用場景
委派模式就好比一個管理者,有上級有下屬,做好中間的協同工作。就像注冊中心
管理分發請求。舉例說明:
spring 中的DispatchServlet就是一個委派者。
浏覽器請求 ,dispatchServlet轉發 ,指定class 方法
- 優勢(servlet和spring mvc轉發對比)
1. 傳統的httpservlet的過程:
浏覽器請求httprequest直接根據path的比對去找,相當的麻煩
```
web.xml 配一堆配置
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.breeze.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name> 與上面的 <servlet-name> 要一緻
<url-pattern>/servlet/LoginServlet</url-pattern>
</servlet-mapping>
```
2. 使用DispatchServlet進行一個服務管理,Dispatchserver會有action的對應資訊管理,httprequest
隻需要和distpatchServelet傳遞任務。Distch會根據自己的政策選擇正确的action去執行。