設計模式 | 4分鐘搞懂10種設計模式

蔡大炮發表於2020-08-23

雖然你覺得大炮肯定是個標題黨,但你終究還是點進來了(別打我,手動狗頭保命),畢竟這價效比也太高了,4分鐘10種,如果是真的就賺大發了。

img

但是大炮可以肯定的告訴你,只要正兒八經的參與過幾個專案,有個一兩年的開發經驗。你肯定已經用過並且可能已經理解了一些設計模式了,只是並不自知而已。今天大炮就來給你好好縷一縷。

file

單例、工廠模式

這兩個設計模式大家都不陌生,很多讀者也都已經自學過了。比如餓漢、懶漢單例模式,簡單、抽象工廠模式。而且這兩個模式,就像兄弟一樣,經常結對出現。所以大炮放到一起來講,我們先來看一行程式碼:

private static final Logger logger = LoggerFactory.getLogger(UserService.class);

非常眼熟吧,這是我們平時用來獲取日誌物件的一段程式碼,但是重點並不是這個 logger 而是 LoggerFactory,顧名思義用來專門用來造 logger 的工廠 ,是一個工廠模式很好的體現。同時作為工廠呢,它又是單例的,畢竟new一個工廠就夠了嘛,自己例項化那麼多個幹嘛呢,單例杜絕了反覆建立和銷燬物件的開銷

同時它又用到了門面模式,這行程式碼是不是很牛,用到了三個設計模式,我們接著往下看。


門面模式

在《阿里巴巴Java開發手冊》中,關於日誌章節中專門有提到:

【強制】應用中不可直接使用日誌系統(Log4j、Logback)中的 API,而應依賴使用日誌框架 SLF4J 中的 API,使用門面模式的日誌框架,有利於維護和各個類的日誌處理方式統一。

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

private static final Logger logger = LoggerFactory.getLogger(Abc.class);

有點感覺沒,門面模式其實就是把自己作為一個介面,自己並不提供真正的實現。什麼,還是沒到位?那我們看看下面這段程式碼:

file

是不是又很眼熟了,下單要幹挺多事情,儲存訂單,儲存訂單項,減庫存...或者還有其他業務,如果所有這些方法的實現都放到 createOrder 這個方法裡面,那程式碼肯定是又長又臭,洋洋灑灑就奔著幾百上千行去了。所以有經驗的你們肯定會抽到好幾個子方法裡面,就像上面一樣,然後挨個呼叫下就行了。(PS,真正的下單程式碼肯定不是這麼玩的,高併發下肯定掛了,看個意思就好)

沒錯,這也是門面模式,createOrder 方法並沒有給出具體的實現,直接呼叫了幾個子方法而已。可能有些讀者心裡已經發出了嘲諷:就這?就這?這也算設計模式?我自己都會寫啦。

file

沒錯,就這~設計模式就是對經驗的總結,然後提出了一些建議和規範。其實並不是多麼高深的東西。現在想想,如果給優秀的我們早生幾年,有些設計模式的創造者還指不定是誰呢。回到上面的程式碼,雖然沒多少技術含量,但對呼叫者而言,我管你怎麼實現,我輕輕調一下 createOrder 方法就完事了。這正是門面模式的精髓所在。

嗯,一行程式碼 get 到了三個設計模式。我們們繼續。


代理模式

代理模式,是一個非常重要的設計模式,不理解它可能會讓你直接無緣大廠。大家一定要學會並理解這個東西。我們先來看一段程式碼:

file

How old are you ? 怎麼老是你 ? 沒錯,還是上面的圖。這次的關注點大家放在 @Transaction 上。註解本身沒啥用,只是作為一個標記,有時候會攜帶一些配置資訊。老開發已經知道我要講啥了,這裡用到了Spring 中一個非常的重要特性:AOP程式設計。而AOP的實現原理就是代理模式。

代理模式的作用就是職責(或者說功能)增強。那Spring幫我們做了哪些增強呢?上圖的部分其實只有CRUD的業務程式碼,事務方面一行都沒有。而沒Spring之前,我們是要這樣寫的:

try {    
    //載入驅動
    Class.forName("com.mysql.jdbc.Driver");    
    //建立連線     
    Connection con = DriverManager.getConnection()   
    //開啟事務    
    con.setAutoCommit(true/false);    
    //真正的、我們每天都要寫的:)    
    C R U D    
    //提交事務    
    con.commit()
} catch (Exception e){   
    //失敗了還要自己手動回滾    
    con.rollback();
} finally {    
    //關閉連線    
    conn.close();
}

寫的虛擬碼,不用在意細節。重要的是如果每個需要用到事務的方法都要這麼重複寫,我們估計早瘋了。這裡Spring就將這些重複的程式碼使用動態代理給我們抽到代理類中去了,所以我們只用關注CRUD就成——從這個角度看,一定程度上造成了程式設計師的懶惰,不是我們不學習,只怪Spring太好用。

代理模式就不繼續發散了,後面會精講。


橋接模式

讓我猜猜看,是不是有些同學第一次聽到這個模式?猜對了待會下面給我留言點贊統計喔,但其實,我們每天都在用。我們們來看一段程式碼:

    //載入驅動    
    Class.forName("com.mysql.jdbc.Driver");    
    //建立連線     
    Connection con = DriverManager.getConnection()

沒錯,大炮又套娃了,用的就是上面的連線資料庫的程式碼。大家每天都要連線資料庫的吧。

先鋪墊一個知識:Java 沒有真正能運算元據庫的方法,只是提供了一些抽象的介面,真正的實現在各個資料庫廠商提供的相應jar包內。mysql就由mysql的開發者提供,oracle的就由oracle的提供。

為什麼我們這個DriverManager能直接get到Connection呢,就是使用了橋接模式。關鍵就在於這個Class.forName("com.mysql.jdbc.Driver"),Class.forName 只是載入了一個類而已,怎麼做到的呢。我們來看這個Driver類到底幹啥了:

file

其實就是一個靜態塊裡面呼叫了java.sql.DriverManager 的 registeDriver方法,然後把mysql 的Driver物件傳了過去,起到了一個搭橋的作用。

多的就不擴充套件了,橋接模式的作用就是連線了一個抽象維度和一個實現維度。這裡的抽象維度就是指 java,實現維度就是指 mysql。其實也可以說橋接的精髓在於“約定”,java 和 各資料庫廠商約定:你們底層實現我不管,但是你們得按照我的規矩來,呼叫我DriverManager的registerDriver方法才行。


中介者模式

聊完橋接模式,不得不聊下中介者模式了。橋接是關注兩個維度的連線,而中介者是關注多個多個維度。作用是統一管理多個多個維度的網狀資源。沒錯,可能你已經想到了,我們天天都在用的註冊中心就是一箇中介者,比如 eureka、zookeeper。

file
如圖,所有的 service 都只和 Service Registry 耦合,而不是直接去依賴其他service ,中介者完美地解決了網狀依賴的複雜關係帶來的程式碼混亂。總體來說中介者還是個比較容易理解的設計模式,這裡就不做太多展開了。


直譯器模式

直譯器模式是個非常簡單的模式,有種自娛自樂的味道,如果你知道它是怎麼自娛自樂的,那你也能跟著“娛樂一下”。來看一個熟悉的東東:

#每隔五秒執行一次times = */5 * * * * ?

你的配置檔案是不是也有corn表示式呢,這就是一個直譯器模式的體現。可能現在你的內心又:

file

直譯器模式定義了一種表示式(或者說是語言),並且也提供瞭解析表示式(語言)的方法。類似的還有正規表示式。Spring中的ExpressionParser也用到了直譯器模式,有興趣的同學可以去看看原始碼。


策略模式

策略模式也比較容易理解,大家在開發和日常生活中也經常用到,也是一個需要重點掌握的設計模式。每天買東西,使用手機支付,就是策略模式的一種體現。策略模式只注重結果,不管使用哪種策略(策略模式要求策略的數量是有限的),最後的結果都是一樣的。比如手機支付,不管使用哪種支付方式,該付多少還是得付多少。

file


原型模式

原型模式也是一種非常簡單,唉,凌晨一點多了。明天繼續寫吧。困了困了。

file

誒大炮又復活了。原型模式也是一種非常簡單的設計模式:給定一個物件例項,通過拷貝的技術建立新的物件,達到物件複用的效果。來看一段程式碼:

UserVo userVO = new UserVO();User user = mapper.selectByPrimaryKey(id);if(user == null){    throw new Exception("未獲取到指定使用者");}/* 拷貝資料,傳給前端大佬渲染頁面 */BeanUtils.copyProperties(user,userVO);return userVO;

是不是非常眼熟了,可能有些讀者內心紛紛表示“是我本人沒錯了”。這裡的BeanUtils.copyProperties 方法就用到了原型模式裡面的 淺拷貝。

稍微提一句,不要使用Apache的 BeanUtils 來copy。效能問題比較嚴重,在《阿里巴巴Java開發手冊》中也有專門提到。如果需要切到Spring的copy方法,也有個坑需要注意下,這兩家的提供的copy,類名相同、方法名也相同。但引數的位置相反,切換時請多加註意並做好測試。


迭代器模式

迭代器模式大家也天天都在用,直接上程式碼:

file

這是一段生成簽名時,拼接引數的演算法。裡面的 Iterator 就是迭代器模式的體現,給集合提供了一個全域性訪問點,通過hasNext()判斷沒有下一個節點,next()方法獲取下個節點,直到所有節點都被訪問過。我們常用的 HashMap ,ArrayList 頂層都是 Collection,而 Collection又繼承了 Iterator 介面。來張ArrayList 的類圖感受下:

file


小結

大炮稍微給大家縷了一遍,有些同學是不是覺得自己又行了,原來不知不覺間,自己竟然用了這麼多設計模式,卻不自知。其實還有很多,比如:

  • 責任鏈模式,Spring security、Shiro這些許可權框架都有用到。
  • 觀察者模式,Spring 的 ContextLoaderListener 就有用到。
  • 模板模式,Spring 中的 JdbcTemplate 有用到。

設計模式無處不在,平時開發我們也無時無刻在享受著設計模式帶來的好處,只是有些同學缺乏一個系統的認知。由於篇幅原因就不再過多介紹了。

file

好吧,標題黨不算太過分,超了三分鐘嘻嘻。

下期預告

今天講的還是比較片面和粗獷的,很多細節都沒有體現出來。這一期相當於是個設計模式的面試嚐鮮版,下一期會對設計模式做一個正式的介紹:

  • 為什麼要用設計模式
  • 設計模式是什麼
  • 設計模式有哪些
  • 設計模式的分類

大概就是上面這些了。然後就開始做具體某個模式的詳細分析和程式碼實現(想先看什麼模式記得私信我喔)。

今天就先到這了,下次去面試被問到用過哪些設計模式,心裡是不是就有底了,直接給他一頓連招13種統統安排上。我們下期見。


往期推薦

file

微信搜尋 java-caidapao ,關注大炮~回覆“面試題”,領取2020最新面試題

相關文章