前言
本文旨在快速梳理常用的設計模式,瞭解每個模式主要針對的是哪些情況以及其基礎特徵,每個模式前都有列舉出一個或多個可以深入閱讀的參考網頁,以供讀者詳細瞭解其實現。
分為三篇文章:
- 上篇:設計模式基礎理念和建立型設計模式
- 中篇:行為型設計模式
- 下篇:結構型設計模式
面試知識點複習手冊
全複習手冊文章導航
點選公眾號下方:技術推文——面試衝刺
快速回憶
行為型
- 責任鏈(Chain Of Responsibility)
- 命令(Command)
- 直譯器(Interpreter)
- 迭代器(Iterator)
- 中介者(Mediator)
- 備忘錄(Memento)
- 觀察者(Observer)
- 狀態(State)
- 策略(Strategy)
- 模板方法(Template Method)
- 訪問者(Visitor)
- 空物件(Null)
理念
首先搞清楚一點,設計模式不是高深技術,不是奇淫技巧。設計模式只是一種設計思想,針對不同的業務場景,用不同的方式去設計程式碼結構,其最最本質的目的是為了解耦,延伸一點的話,還有為了可擴充套件性和健壯性,但是這都是建立在解耦的基礎之上。
高內聚低耦合
高內聚:系統中A、B兩個模組進行互動,如果修改了A模組,不影響模組B的工作,那麼認為A有足夠的內聚。
低耦合:就是A模組與B模組存在依賴關係,那麼當B發生改變時,A模組仍然可以正常工作,那麼就認為A與B是低耦合的。
行為型
責任鏈(Chain Of Responsibility)
意圖
使多個物件都有機會處理請求,從而避免請求的傳送者和接收者之間的耦合關係。將這些物件連成一條鏈,並沿著這條鏈傳送該請求,直到有一個物件處理它為止。
在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個物件不能處理該請求,那麼它會把相同的請求傳給下一個接收者,依此類推。
職責和角色
- Handler:處理類的抽象父類
- concreteHandler:具體的處理類
設計使用責任鏈的基本流程
- 組織物件鏈:將某人物的所有職責執行物件以鏈的形式加以組織起來
- 訊息或請求的傳遞:將訊息或請求沿著物件鏈傳遞,讓處於物件鏈中的物件得到處理機會
- 物件鏈中物件的職責分配:不同物件完成不同職責
- 任務的完成:物件鏈末尾的物件結束任務並停止訊息或請求的繼續傳遞。
應用例項
紅樓夢中的"擊鼓傳花"。 JS 中的事件冒泡。 JAVA WEB 中 Apache Tomcat 對 Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter,springMVC的攔截器
何時使用:在處理訊息的時候以過濾很多道。
如何解決:攔截的類都實現統一介面。
關鍵程式碼:Handler 裡面聚合它自己,在 HandlerRequest 裡判斷是否合適,如果沒達到條件則向下傳遞,向誰傳遞之前 set 進去。
實現舉例
處理器的抽象類
package com.mym.designmodel.CoRModel;
/**
* 職責:Handler 職責類的抽象父類
*/
public abstract class AbstractCarHandler {
AbstractCarHandler carHandler = null;
public abstract void carHandler();
public AbstractCarHandler setNextCarHandler(AbstractCarHandler nextCarHandler){
this.carHandler = nextCarHandler;
return this.carHandler;
}
/**職責下傳*/
protected void doChain(){
if(this.carHandler != null){
this.carHandler.carHandler();
}
}
}
複製程式碼
責任鏈一個執行者1
package com.mym.designmodel.CoRModel;
/**
* 職責:concreteHandler 具體的處理類
*/
public class CarHeadHandler extends AbstractCarHandler {
@Override
public void carHandler() {
System.out.println("處理車的head!");
//下傳
this.doChain();
}
}
複製程式碼
責任鏈一個執行者2
package com.mym.designmodel.CoRModel;
/**
* 職責:concreteHandler 具體的處理類
*/
public class CarBodyHandler extends AbstractCarHandler {
@Override
public void carHandler() {
System.out.println("處理車的body!");
//下傳
this.doChain();
}
}
複製程式碼
責任鏈一個執行者3
package com.mym.designmodel.CoRModel;
/**
* 職責:concreteHandler 具體的處理類
*/
public class CarTailHandler extends AbstractCarHandler {
@Override
public void carHandler() {
System.out.println("處理車的tail!");
//下傳
this.doChain();
}
}
複製程式碼
客戶端client
package com.mym.designmodel.CoRModel;
/**
* 測試
*/
public class MainClass {
public static void main(String[] args) {
AbstractCarHandler carheadHandle = new CarHeadHandler();
AbstractCarHandler carbodyHandle = new CarBodyHandler();
AbstractCarHandler carTailHandler = new CarTailHandler();
//組裝責任鏈
carheadHandle.setNextCarHandler(carbodyHandle).setNextCarHandler(carTailHandler);
//鏈頭部開始執行
carheadHandle.carHandler();
}
}
複製程式碼
JDK
命令(Command)
意圖
命令模式是為了解決命令的請求者和命令的實現者之間的耦合關係。
解決了這種耦合的好處我認為主要有兩點:
1.更方便的對命令進行擴充套件(注意:這不是主要的優勢,後面會提到)
2.對多個命令的統一控制(這種控制包括但不限於:佇列、撤銷/恢復、記錄日誌等等)
應用例項
struts 1 中的 action 核心控制器 ActionServlet 只有一個,相當於 Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當於具體的 Command。
類圖
- Command:命令
- Receiver:命令接收者,也就是命令真正的執行者
- Invoker:通過它來呼叫命令
- Client:可以設定命令與命令的接收者
JDK
直譯器(Interpreter)
意圖
所謂直譯器模式就是定義語言的文法,並且建立一個直譯器來解釋該語言中的句子。
這種模式實現了一個表示式介面,該介面解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。
應用例項
編譯器、運算表示式計算。
JDK
- java.util.Pattern
- java.text.Normalizer
- All subclasses of java.text.Format
- javax.el.ELResolver
迭代器(Iterator)
意圖
提供一種順序訪問聚合物件元素的方法,並且不暴露聚合物件的內部表示。
迭代器模式的優缺點
優點
①簡化了遍歷方式,對於物件集合的遍歷,還是比較麻煩的,對於陣列或者有序列表,我們尚可以通過遊標來取得,但使用者需要在對集合瞭解很清楚的前提下,自行遍歷物件,但是對於hash表來說,使用者遍歷起來就比較麻煩了。而引入了迭代器方法後,使用者用起來就簡單的多了。
②可以提供多種遍歷方式,比如說對有序列表,我們可以根據需要提供正序遍歷,倒序遍歷兩種迭代器,使用者用起來只需要得到我們實現好的迭代器,就可以方便的對集合進行遍歷了。
③封裝性良好,使用者只需要得到迭代器就可以遍歷,而對於遍歷演算法則不用去關心。
缺點
對於比較簡單的遍歷(像陣列或者有序列表),使用迭代器方式遍歷較為繁瑣,大家可能都有感覺,像ArrayList,我們寧可願意使用for迴圈和get方法來遍歷集合。
JDK
中介者(Mediator)
意圖
集中相關物件之間複雜的溝通和控制方式。
實現
Alarm(鬧鐘)、CoffeePot(咖啡壺)、Calendar(日曆)、Sprinkler(噴頭)是一組相關的物件,在某個物件的事件產生時需要去操作其它物件,形成了下面這種依賴結構:
使用中介者模式可以將複雜的依賴結構變成星形結構:
JDK
- All scheduleXXX() methods of java.util.Timer
- java.util.concurrent.Executor#execute()
- submit() and invokeXXX() methods of java.util.concurrent.ExecutorService
- scheduleXXX() methods of java.util.concurrent.ScheduledExecutorService
- java.lang.reflect.Method#invoke()
備忘錄(Memento)
意圖
在不違反封裝的情況下獲得物件的內部狀態,從而在需要時可以將物件恢復到最初狀態。
主要解決
所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個物件的內部狀態,並在該物件之外儲存這個狀態,這樣可以在以後將物件恢復到原先儲存的狀態。
應用例項
1、後悔藥。 2、打遊戲時的存檔。 3、Windows 裡的 ctri + z。 4、IE 中的後退。 5、資料庫的事務管理。
JDK
- java.io.Serializable
觀察者(Observer)
意圖
觀察者模式是物件的行為模式,又叫釋出-訂閱(Publish/Subscribe)模式、模型-檢視(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。這個主題物件在狀態上發生變化時,會通知所有觀察者物件,使它們能夠自動更新自己。
何時使用
一個物件(目標物件)的狀態發生改變,所有的依賴物件(觀察者物件)都將得到通知,進行廣播通知。
實現舉例
天氣資料佈告板會在天氣資訊發生改變時更新其內容,佈告板有多個,並且在將來會繼續增加。
JDK
狀態(State)
意圖
允許物件在內部狀態改變時改變它的行為,物件看起來好像修改了它所屬的類。
應用例項
考慮一個線上投票系統的應用,要實現控制同一個使用者只能投一票,如果一個使用者反覆投票,而且投票次數超過5次,則判定為惡意刷票,要取消該使用者投票的資格,當然同時也要取消他所投的票;如果一個使用者的投票次數超過8次,將進入黑名單,禁止再登入和使用系統。
策略(Strategy)
意圖
定義一系列演算法,封裝每個演算法,並使它們可以互換。
策略模式和狀態模式的區別:
之所以說狀態模式是策略模式的孿生兄弟,是因為它們的UML圖是一樣的,但意圖卻完全不一樣,**策略模式是讓使用者指定更換的策略演算法,而狀態模式是狀態在滿足一定條件下的自動更換,使用者無法指定狀態,最多隻能設定初始狀態。 **
策略模式可以讓演算法獨立於使用它的客戶端。
具體場景實現
假設現在要設計一個販賣各類書籍的電子商務網站的購物車系統。一個最簡單的情況就是把所有貨品的單價乘上數量,但是實際情況肯定比這要複雜。比如,本網站可能對所有的高階會員提供每本20%的促銷折扣;對中級會員提供每本10%的促銷折扣;對初級會員沒有折扣。
根據描述,折扣是根據以下的幾個演算法中的一個進行的:
演算法一:對初級會員沒有折扣。
演算法二:對中級會員提供10%的促銷折扣。
演算法三:對高階會員提供20%的促銷折扣。
public static void main(String[] args) {
//選擇並建立需要使用的策略物件
MemberStrategy strategy = new AdvancedMemberStrategy();
//建立環境
Price price = new Price(strategy);
//計算價格
double quote = price.quote(300);
System.out.println("圖書的最終價格為:" + quote);
}
複製程式碼
策略模式對多型的使用
通過讓環境類持有一個抽象策略類(超類)的引用,在生成環境類例項物件時,讓該引用指向具體的策略子類。再對應的方法呼叫中,就會通過Java的多型,呼叫對應策略子類的方法。從而可以相互替換,不需要修改環境類內部的實現。同時,在有新的需求的情況下,也只需要修改策略類即可,降低與環境類之間的耦合度。
策略模式和工廠方法的異同
工廠模式和策略模式的區別在於例項化一個物件的位置不同,對工廠模式而言,例項化物件是放在服務端的,即放在了工廠類裡面; 而策略模式例項化物件的操作在客戶端
工廠模式要求服務端的銷售部門足夠靈敏,而策略模式由於對策略進行了封裝,所以他的銷售部門比較傻,需要客戶提供足夠能區分使用哪種策略的引數,而這最好的就是該策略的例項了。
JDK
- java.util.Comparator#compare()
- javax.servlet.http.HttpServlet
- javax.servlet.Filter#doFilter()
模板方法(Template Method)
典型用例:Spring
定義
模板方法模式是類的行為模式。
準備一個抽象類,將部分邏輯以具體方法以及具體建構函式的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。這就是模板方法模式的用意。
結構
模板方法中的方法可以分為兩大類:模板方法和基本方法。
模板方法
一個模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總演算法或一個總行為的方法。
一個抽象類可以有任意多個模板方法,而不限於一個。每一個模板方法都可以呼叫任意多個具體方法。
基本方法
基本方法又可以分為三種
-
抽象方法:一個抽象方法由抽象類宣告,由具體子類實現。在Java語言裡抽象方法以abstract關鍵字標示。
-
具體方法:一個具體方法由抽象類宣告並實現,而子類並不實現或置換。
-
鉤子方法:一個鉤子方法由抽象類宣告並實現,而子類會加以擴充套件。通常抽象類給出的實現是一個空實現,作為方法的預設實現。
預設鉤子方法
一個鉤子方法常常由抽象類給出一個空實現作為此方法的預設實現。這種空的鉤子方法叫做“Do Nothing Hook”。具體模版類中可以選擇是否重寫鉤子方法,通常重寫鉤子方法是為了對模版方法中的步驟進行控制,判斷鉤子方法中的狀態,是否進行下一步操作。
使用場景
模板方法模式是基於繼承的程式碼複用技術,它體現了物件導向的諸多重要思想,是一種使用較為頻繁的模式。模板方法模式廣泛應用於框架設計中,以確保通過父類來控制處理流程的邏輯順序(如框架的初始化,測試流程的設定等)。
JDK
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
訪問者(Visitor)
什麼是訪問者模式?
比如我有一個賬單,賬單有收入,支出兩個固定方法。但是訪問賬單的人不確定,有可能是一個或者多個。
訪問者模式有兩個特點
一般被訪問的東西所持有的方法是固定的,就像賬單隻有收入和支出兩個功能。而訪問者是不固定的。
資料操作與資料結構相分離:頻繁的更改資料,但不結構不變。比如:雖然每一天賬單的資料都會變化(資料變化),但是隻有兩類資料,就是支出和收入(結構不變)。
程式碼
見參考網頁
空物件(Null)
意圖
使用什麼都不做的空物件來代替 NULL。
一個方法返回 NULL,意味著方法的呼叫端需要去檢查返回值是否是 NULL,這麼做會導致非常多的冗餘的檢查程式碼。並且如果某一個呼叫端忘記了做這個檢查返回值,而直接使用返回的物件,那麼就有可能丟擲空指標異常。
-----正文結束-----
更多精彩文章,請查閱我的部落格或關注我的公眾號:Rude3Knife
全複習手冊文章導航
點選公眾號下方:技術推文——面試衝刺
知識點複習手冊文章推薦
- Java基礎知識點面試手冊
- Java容器(List、Set、Map)知識點快速複習手冊
- Java併發知識點快速複習手冊(上)
- Java併發知識點快速複習手冊(下)
- Java虛擬機器知識點快速複習手冊(上)
- Java虛擬機器知識點快速複習手冊(下)
- 快速梳理23種常用的設計模式
- Redis基礎知識點面試手冊
- Leetcode題解分類彙總(前150題)
- 面試常問的小演算法總結
- 查詢演算法總結及其部分演算法實現Python/Java
- 排序演算法實現與總結Python/Java
- HTTP應知應會知識點複習手冊(上)
- HTTP應知應會知識點複習手冊(下)
- ......等(請檢視全複習手冊導航)
關注我
我是蠻三刀把刀,目前為後臺開發工程師。主要關注後臺開發,網路安全,Python爬蟲等技術。
來微信和我聊聊:yangzd1102
Github:github.com/qqxx6661
原創部落格主要內容
- 筆試面試複習知識點手冊
- Leetcode演算法題解析(前150題)
- 劍指offer演算法題解析
- Python爬蟲相關技術分析和實戰
- 後臺開發相關技術分析和實戰
同步更新以下部落格
1. Csdn
擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發、面試助攻手冊
2. 知乎
擁有專欄:碼農面試助攻手冊
3. 掘金
4. 簡書
個人公眾號:Rude3Knife
如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~