快速梳理23种常用的设计模式
本文旨在快速梳理常用的設計模式,了解每個模式主要針對的是哪些情況以及其基礎特征,每個模式前都有列舉出一個或多個可以深入閱讀的參考網頁,以供讀者詳細了解其實現。
快速回憶
一、創建型
- 單例(Singleton)
- 工廠模式
- 簡單工廠(Simple Factory)
- 工廠方法(Factory Method)
- 抽象工廠(Abstract Factory)
- 生成器(Builder)
- 原型模式(Prototype)
二、行為型
- 責任鏈(Chain Of Responsibility)
- 命令(Command)
- 解釋器(Interpreter)
- 迭代器(Iterator)
- 中介者(Mediator)
- 備忘錄(Memento)
- 觀察者(Observer)
- 狀態(State)
- 策略(Strategy)
- 模板方法(Template Method)
- 訪問者(Visitor)
- 空對象(Null)
三、結構型
- 適配器(Adapter)
- 裝飾器(Decorator)
- 代理模式(Proxy)
- 外觀模式/門面模式(Facade)
- 橋接模式(Bridge Pattern)
- 組合模式(Composite)
- 享元模式(Flyweight)
理念
首先搞清楚一點,設計模式不是高深技術,不是奇淫技巧。設計模式只是一種設計思想,針對不同的業務場景,用不同的方式去設計代碼結構,其最最本質的目的是為了解耦,延伸一點的話,還有為了可擴展性和健壯性,但是這都是建立在解耦的基礎之上。
高內聚低耦合
高內聚:系統中A、B兩個模塊進行交互,如果修改了A模塊,不影響模塊B的工作,那么認為A有足夠的內聚。
低耦合:就是A模塊與B模塊存在依賴關系,那么當B發生改變時,A模塊仍然可以正常工作,那么就認為A與B是低耦合的。
創建型
單例模式
請參考Github詳細解釋,下面的一點僅供快速復習。Github寫的很好。
同時參考:
http://blog.jobbole.com/109449/
意圖
確保一個類只有一個實例,并提供該實例的全局訪問點。
類圖
使用一個私有構造函數、一個私有靜態變量以及一個公有靜態函數來實現。
私有構造函數保證了不能通過構造函數來創建對象實例,只能通過公有靜態函數返回唯一的私有靜態變量。
實現
懶漢式——線程安全/雙重校驗
單例是為了保證系統中只有一個實例,其關鍵點有
一.私有化構造函數
二.聲明靜態單例對象
三.構造單例對象之前要加鎖(lock一個靜態的object對象)或者方法上加synchronized。
四.需要兩次檢測單例實例是否已經被構造,分別在鎖之前和鎖之后
使用lock(obj)
public class Singleton { private Singleton() {} //關鍵點0:構造函數是私有的private volatile static Singleton single; //關鍵點1:聲明單例對象是靜態的private static object obj= new object();public static Singleton GetInstance() //通過靜態方法來構造對象{ if (single == null) //關鍵點2:判斷單例對象是否已經被構造{ lock(obj) //關鍵點3:加線程鎖{if(single == null) //關鍵點4:二次判斷單例是否已經被構造{single = new Singleton(); }}} return single; } }使用synchronized (Singleton.class)
public class Singleton {private Singleton() {}private volatile static Singleton uniqueInstance;public static Singleton getUniqueInstance() {if (uniqueInstance == null) {synchronized (Singleton.class) {if (uniqueInstance == null) {uniqueInstance = new Singleton();}}}return uniqueInstance;} }可能提問
0.為何要檢測兩次?
如果兩個線程同時執行 if 語句,那么兩個線程就會同時進入 if 語句塊內。雖然在if語句塊內有加鎖操作,但是兩個線程都會執行 uniqueInstance = new Singleton(); 這條語句,只是先后的問題,也就是說會進行兩次實例化,從而產生了兩個實例。因此必須使用雙重校驗鎖,也就是需要使用兩個 if 語句。
1.構造函數能否公有化?
不行,單例類的構造函數必須私有化,單例類不能被實例化,單例實例只能靜態調用。
2.lock住的對象為什么要是object對象,可以是int嗎?
不行,鎖住的必須是個引用類型。如果鎖值類型,每個不同的線程在聲明的時候值類型變量的地址都不一樣,那么上個線程鎖住的東西下個線程進來會認為根本沒鎖。
3.uniqueInstance 采用 volatile 關鍵字修飾
uniqueInstance = new Singleton(); 這段代碼其實是分為三步執行。
分配內存空間初始化對象將 uniqueInstance 指向分配的內存地址但是由于 JVM 具有指令重排的特性,有可能執行順序變為了 1>3>2
public class Singleton {private volatile static Singleton uniqueInstance;private Singleton(){}public static Singleton getInstance(){if(uniqueInstance == null){// B線程檢測到uniqueInstance不為空synchronized(Singleton.class){if(uniqueInstance == null){uniqueInstance = new Singleton();// A線程被指令重排了,剛好先賦值了;但還沒執行完構造函數。}}}return uniqueInstance;// 后面B線程執行時將引發:對象尚未初始化錯誤。} }餓漢式-線程安全
線程不安全問題主要是由于 uniqueInstance 被實例化了多次,如果 uniqueInstance 采用直接實例化的話,就不會被實例化多次,也就不會產生線程不安全問題。但是直接實例化的方式也丟失了延遲實例化帶來的節約資源的優勢。
private static Singleton uniqueInstance = new Singleton();靜態內部類實現
當 Singleton 類加載時,靜態內部類 SingletonHolder 沒有被加載進內存。只有當調用 getUniqueInstance() 方法從而觸發 SingletonHolder.INSTANCE 時 SingletonHolder 才會被加載,此時初始化 INSTANCE 實例。
這種方式不僅具有延遲初始化的好處,而且由虛擬機提供了對線程安全的支持。
public class Singleton {private Singleton() {}private static class SingletonHolder {private static final Singleton INSTANCE = new Singleton();}public static Singleton getUniqueInstance() {return SingletonHolder.INSTANCE;} }枚舉實現
這是單例模式的最佳實踐,它實現簡單,并且在面對復雜的序列化或者反射攻擊的時候,能夠防止實例化多次。
public enum Singleton {uniqueInstance; }考慮以下單例模式的實現,該 Singleton 在每次序列化的時候都會創建一個新的實例,為了保證只創建一個實例,必須聲明所有字段都是 transient,并且提供一個 readResolve() 方法。
public class Singleton implements Serializable {private static Singleton uniqueInstance;private Singleton() {}public static synchronized Singleton getUniqueInstance() {if (uniqueInstance == null) {uniqueInstance = new Singleton();}return uniqueInstance;} }如果不使用枚舉來實現單例模式,會出現反射攻擊,因為通過 setAccessible() 方法可以將私有構造函數的訪問級別設置為 public,然后調用構造函數從而實例化對象。如果要防止這種攻擊,需要在構造函數中添加防止實例化第二個對象的代碼。
從上面的討論可以看出,解決序列化和反射攻擊很麻煩,而枚舉實現不會出現這兩種問題,所以說枚舉實現單例模式是最佳實踐。
使用場景
- Logger Classes
- Configuration Classes
- Accesing resources in shared mode
- Factories implemented as Singletons
簡單/靜態工廠(Simple Factory)
https://www.jianshu.com/p/d1b6731c1c0e
定義
在創建一個對象時不向客戶暴露內部細節,并提供一個創建對象的通用接口。
在簡單工廠模式中,可以根據參數的不同返回不同類的實例。
簡單工廠模式專門定義一個類來負責創建其他類的實例
結構
簡單工廠模式包含如下角色:
-
Factory:工廠角色
工廠角色負責實現創建所有實例的內部邏輯 -
Product:抽象產品角色
抽象產品角色是所創建的所有對象的父類,負責描述所有實例所共有的公共接口 -
ConcreteProduct:具體產品角色
具體產品角色是創建目標,所有創建的對象都充當這個角色的某個具體類的實例。
實現
public class Test {public static void main(String[] args) {String loginType = "password";String name = "name";String password = "password";Login login = LoginManager.factory(loginType);boolean bool = login.verify(name, password);if (bool) {/*** 業務邏輯*/} else {/*** 業務邏輯*/}} }優缺點
優點
構造容易,邏輯簡單。
缺點
1.簡單工廠模式中的if else判斷非常多,完全是Hard Code,如果有一個新產品要加進來,就要同時添加一個新產品類,并且必須修改工廠類,再加入一個 else if 分支才可以, 這樣就違背了 “開放-關閉原則“中的對修改關閉的準則了。
2.一個工廠類中集合了所有的類的實例創建邏輯,違反了高內聚的責任分配原則,將全部的創建邏輯都集中到了一個工廠類當中,所有的業務邏輯都在這個工廠類中實現。什么時候它不能工作了,整個系統都會受到影響。因此一般只在很簡單的情況下應用,比如當工廠類負責創建的對象比較少時。
3.簡單工廠模式由于使用了靜態工廠方法,造成工廠角色無法形成基于繼承的等級結構。
適用環境
- 工廠類負責創建的對象比較少:由于創建的對象較少,不會造成工廠方法中的業務邏輯太過復雜。
JDK
①JDK類庫中廣泛使用了簡單工廠模式,如工具類java.text.DateFormat,它用于格式化一個本地日期或者時間。
public final static DateFormat getDateInstance(); public final static DateFormat getDateInstance(int style); public final static DateFormat getDateInstance(int style,Locale locale);②Java加密技術
獲取不同加密算法的密鑰生成器:
創建密碼器:
Cipher cp = Cipher.getInstance("DESede");工廠方法(Factory Method)
https://www.jianshu.com/p/1cf9859e0f7c
意圖
又稱為工廠模式/虛擬構造器(Virtual Constructor)模式/多態工廠(Polymorphic Factory)模式
即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
使用動機
不再設計一個按鈕工廠類來統一負責所有產品的創建,而是將具體按鈕的創建過程交給專門的工廠子類去完成。
我們先定義一個抽象的按鈕工廠類,再定義具體的工廠類來生成圓形按鈕、矩形按鈕、菱形按鈕等,它們實現在抽象按鈕工廠類中定義的方法。這種抽象化的結果使這種結構可以在不修改具體工廠類的情況下引進新的產品,如果出現新的按鈕類型,只需要為這種新類型的按鈕創建一個具體的工廠類就可以獲得該新按鈕的實例,這一特點無疑使得工廠方法模式具有超越簡單工廠模式的優越性,更加符合“開閉原則”。
角色
-
Product:抽象產品,工廠方法模式所創建的對象的超類,也就是所有產品類的共同父類或共同擁有的接口。在實際的系統中,這個角色也常常使用抽象類實現。
-
ConcreteProduct:具體產品,這個角色實現了抽象產品(Product)所聲明的接口,工廠方法模式所創建的每一個對象都是某個具體產品的實例。
-
Factory:抽象工廠,擔任這個角色的是工廠方法模式的核心,任何在模式中創建對象的工廠類必須實現這個接口。在實際的系統中,這個角色也常常使用抽象類實現。
-
ConcreteFactory:具體工廠,擔任這個角色的是實現了抽象工廠接口的具體Java類。具體工廠角色含有與業務密切相關的邏輯,并且受到使用者的調用以創建具體產品對象。
實現
看鏈接內
客戶端調用
static void Main(string[] args){//先給我來個燈泡ICreator creator = new BulbCreator();ILight light = creator.CreateLight();light.TurnOn();light.TurnOff();//再來個燈管看看creator = new TubeCreator();light = creator.CreateLight();light.TurnOn();light.TurnOff();}優缺點
優點
①在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
②工廠方法模式之所以又被稱為多態工廠模式,是因為所有的具體工廠類都具有同一抽象父類。
③使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”,這點比簡單工廠模式更優秀。
缺點
①在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的復雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
②由于考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。
JDK
- JDBC中的工廠方法:
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
抽象工廠(Abstract Factory)
https://www.jianshu.com/p/d6622f3e71ed
定義
產品等級結構 :產品等級結構即產品的繼承結構,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構,抽象電視機是父類,而具體品牌的電視機是其子類。
產品族 :在抽象工廠模式中,產品族是指由同一個工廠生產的,位于不同產品等級結構中的一組產品,如海爾電器工廠生產的海爾電視機、海爾電冰箱,海爾電視機位于電視機產品等級結構中,海爾電冰箱位于電冰箱產品等級結構中。
抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態。
抽象工廠模式與工廠方法模式最大的區別
工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品對象的創建 。
角色
抽象工廠模式包含如下角色:
- AbstractFactory:抽象工廠
- ConcreteFactory:具體工廠
- AbstractProduct:抽象產品
- Product:具體產品
實現
抽象產品: 蘋果系列
public interface Apple{void AppleStyle();}具體產品:iphone
public class iphone implements Apple{public void AppleStyle(){Console.WriteLine("Apple's style: iPhone!");}}抽象工廠
public interface Factory{Apple createAppleProduct();Sumsung createSumsungProduct();}手機工廠
public class Factory_Phone implements Factory{public Apple createAppleProduct(){return new iphone();}public Sumsung createSumsungProduct(){return new note2();}}調用
public static void Main(string[] args){//采購商要一臺iPad和一臺TabFactory factory = new Factory_Pad();Apple apple = factory.createAppleProduct();apple.AppleStyle();Sumsung sumsung = factory.createSumsungProduct();sumsung.BangziStyle();//采購商又要一臺iPhone和一臺Note2factory = new Factory_Phone();apple = factory.createAppleProduct();apple.AppleStyle();sumsung = factory.createSumsungProduct();sumsung.BangziStyle();}優缺點
優點
①應用抽象工廠模式可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛的應用。
②增加新的具體工廠和產品族很方便,因為一個具體的工廠實現代表的是一個產品族,無須修改已有系統,符合“開閉原則”。
缺點
開閉原則的傾斜性(增加新的工廠和產品族容易,增加新的產品等級結構麻煩)
適用環境
在以下情況下可以使用抽象工廠模式:
①一個系統不應當依賴于產品類實例如何被創建、組合和表達的細節,這對于所有類型的工廠模式都是重要的。
②系統中有多于一個的產品族,而每次只使用其中某一產品族。(與工廠方法模式的區別)
③屬于同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。
④系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴于具體實現。
JDK
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
生成器(Builder)
https://blog.csdn.net/c275046758/article/details/50540789
意圖
封裝一個對象的構造過程,并允許按步驟構造。
實現
JDK
- java.lang.StringBuilder
- java.nio.ByteBuffer
- java.lang.StringBuffer
- java.lang.Appendable
- Apache Camel builders
原型模式(Prototype)
https://www.cnblogs.com/lwbqqyumidi/p/3746821.html
意圖
通過一個已經存在的對象,復制出更多的具有與此對象具有相同類型的新的對象。
淺復制 clone():
我們知道,一個類的定義中包括屬性和方法。屬性用于表示對象的狀態,方法用于表示對象所具有的行為。其中,屬性既可以是Java中基本數據類型,也可以是引用類型。
Java中的淺復制通常使用**clone()**方式完成。
當進淺復制時,clone函數返回的是一個引用,指向的是新的clone出來的對象,此對象與原對象分別占用不同的堆空間。同時,復制出來的對象具有與原對象一致的狀態。
此處對象一致的狀態是指:復制出的對象與原對象中的屬性值完全相等==。
深復制 deepclone():
Java中的深復制一般是通過對象的序列化和反序列化得以實現。序列化時,需要實現Serializable接口。
從輸出結果中可以看出,深復制不僅在堆內存上開辟了空間以存儲復制出的對象,甚至連對象中的引用類型的屬性所指向的對象也得以復制,重新開辟了堆空間存儲。
JDK
- java.lang.Object#clone()
- deepclone()
行為型
責任鏈(Chain Of Responsibility)
https://blog.csdn.net/maoyuanming0806/article/details/80183494
意圖
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈發送該請求,直到有一個對象處理它為止。
在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者,依此類推。
職責和角色
- 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 {@Overridepublic void carHandler() {System.out.println("處理車的head!");//下傳this.doChain();} }責任鏈一個執行者2
package com.mym.designmodel.CoRModel;/*** 職責:concreteHandler 具體的處理類*/ public class CarBodyHandler extends AbstractCarHandler {@Overridepublic void carHandler() {System.out.println("處理車的body!");//下傳this.doChain();} }責任鏈一個執行者3
package com.mym.designmodel.CoRModel;/*** 職責:concreteHandler 具體的處理類*/ public class CarTailHandler extends AbstractCarHandler {@Overridepublic 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
- java.util.logging.Logger#log()
- Apache Commons Chain
- javax.servlet.Filter#doFilter()
命令(Command)
https://www.cnblogs.com/konck/p/4199907.html
意圖
命令模式是為了解決命令的請求者和命令的實現者之間的耦合關系。
解決了這種耦合的好處我認為主要有兩點:
1.更方便的對命令進行擴展(注意:這不是主要的優勢,后面會提到)
2.對多個命令的統一控制(這種控制包括但不限于:隊列、撤銷/恢復、記錄日志等等)
應用實例
struts 1 中的 action 核心控制器 ActionServlet 只有一個,相當于 Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當于具體的 Command。
類圖
- Command:命令
- Receiver:命令接收者,也就是命令真正的執行者
- Invoker:通過它來調用命令
- Client:可以設置命令與命令的接收者
JDK
- java.lang.Runnable
- Netflix Hystrix
- javax.swing.Action
解釋器(Interpreter)
https://www.cnblogs.com/chenssy/p/3346427.html
意圖
所謂解釋器模式就是定義語言的文法,并且建立一個解釋器來解釋該語言中的句子。
這種模式實現了一個表達式接口,該接口解釋一個特定的上下文。這種模式被用在 SQL 解析、符號處理引擎等。
應用實例
編譯器、運算表達式計算。
JDK
- java.util.Pattern
- java.text.Normalizer
- All subclasses of java.text.Format
- javax.el.ELResolver
迭代器(Iterator)
https://www.jianshu.com/p/3d0406a01b73
意圖
提供一種順序訪問聚合對象元素的方法,并且不暴露聚合對象的內部表示。
迭代器模式的優缺點
優點
①簡化了遍歷方式,對于對象集合的遍歷,還是比較麻煩的,對于數組或者有序列表,我們尚可以通過游標來取得,但用戶需要在對集合了解很清楚的前提下,自行遍歷對象,但是對于hash表來說,用戶遍歷起來就比較麻煩了。而引入了迭代器方法后,用戶用起來就簡單的多了。
②可以提供多種遍歷方式,比如說對有序列表,我們可以根據需要提供正序遍歷,倒序遍歷兩種迭代器,用戶用起來只需要得到我們實現好的迭代器,就可以方便的對集合進行遍歷了。
③封裝性良好,用戶只需要得到迭代器就可以遍歷,而對于遍歷算法則不用去關心。
缺點
對于比較簡單的遍歷(像數組或者有序列表),使用迭代器方式遍歷較為繁瑣,大家可能都有感覺,像ArrayList,我們寧可愿意使用for循環和get方法來遍歷集合。
JDK
- java.util.Iterator
- java.util.Enumeration
中介者(Mediator)
http://www.runoob.com/design-pattern/mediator-pattern.html
意圖
集中相關對象之間復雜的溝通和控制方式。
實現
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)
https://blog.csdn.net/o279642707/article/details/60767258
意圖
在不違反封裝的情況下獲得對象的內部狀態,從而在需要時可以將對象恢復到最初狀態。
主要解決
所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個對象的內部狀態,并在該對象之外保存這個狀態,這樣可以在以后將對象恢復到原先保存的狀態。
應用實例
1、后悔藥。
2、打游戲時的存檔。
3、Windows 里的 ctri + z。
4、IE 中的后退。
5、數據庫的事務管理。
JDK
- java.io.Serializable
觀察者(Observer)
https://www.jianshu.com/p/fc4554cda68d
意圖
觀察者模式是對象的行為模式,又叫發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
何時使用
一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知,進行廣播通知。
實現舉例
天氣數據布告板會在天氣信息發生改變時更新其內容,布告板有多個,并且在將來會繼續增加。
JDK
- java.util.Observer
- java.util.EventListener
- javax.servlet.http.HttpSessionBindingListener
- RxJava
狀態(State)
https://www.cnblogs.com/java-my-life/archive/2012/06/08/2538146.html
意圖
允許對象在內部狀態改變時改變它的行為,對象看起來好像修改了它所屬的類。
應用實例
考慮一個在線投票系統的應用,要實現控制同一個用戶只能投一票,如果一個用戶反復投票,而且投票次數超過5次,則判定為惡意刷票,要取消該用戶投票的資格,當然同時也要取消他所投的票;如果一個用戶的投票次數超過8次,將進入黑名單,禁止再登錄和使用系統。
策略(Strategy)
https://www.jianshu.com/p/7fa8ad000a97
https://www.cnblogs.com/wenjiang/p/3352041.html
意圖
定義一系列算法,封裝每個算法,并使它們可以互換。
策略模式和狀態模式的區別:
之所以說狀態模式是策略模式的孿生兄弟,是因為它們的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)
https://www.jianshu.com/p/cc391b56bd0e
典型用例:Spring
定義
模板方法模式是類的行為模式。
準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然后聲明一些抽象方法來迫使子類實現剩余的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩余的邏輯有不同的實現。這就是模板方法模式的用意。
結構
模板方法中的方法可以分為兩大類:模板方法和基本方法。
模板方法
一個模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個總算法或一個總行為的方法。
一個抽象類可以有任意多個模板方法,而不限于一個。每一個模板方法都可以調用任意多個具體方法。
基本方法
基本方法又可以分為三種
-
抽象方法:一個抽象方法由抽象類聲明,由具體子類實現。在Java語言里抽象方法以abstract關鍵字標示。
-
具體方法:一個具體方法由抽象類聲明并實現,而子類并不實現或置換。
-
鉤子方法:一個鉤子方法由抽象類聲明并實現,而子類會加以擴展。通常抽象類給出的實現是一個空實現,作為方法的默認實現。
默認鉤子方法
一個鉤子方法常常由抽象類給出一個空實現作為此方法的默認實現。這種空的鉤子方法叫做“Do Nothing Hook”。具體模版類中可以選擇是否重寫鉤子方法,通常重寫鉤子方法是為了對模版方法中的步驟進行控制,判斷鉤子方法中的狀態,是否進行下一步操作。
使用場景
模板方法模式是基于繼承的代碼復用技術,它體現了面向對象的諸多重要思想,是一種使用較為頻繁的模式。模板方法模式廣泛應用于框架設計中,以確保通過父類來控制處理流程的邏輯順序(如框架的初始化,測試流程的設置等)。
JDK
- java.util.Collections#sort()
- java.io.InputStream#skip()
- java.io.InputStream#read()
- java.util.AbstractList#indexOf()
訪問者(Visitor)
https://www.jianshu.com/p/80b9cd7c0da5
什么是訪問者模式?
比如我有一個賬單,賬單有收入,支出兩個固定方法。但是訪問賬單的人不確定,有可能是一個或者多個。
訪問者模式有兩個特點
一般被訪問的東西所持有的方法是固定的,就像賬單只有收入和支出兩個功能。而訪問者是不固定的。
數據操作與數據結構相分離:頻繁的更改數據,但不結構不變。比如:雖然每一天賬單的數據都會變化(數據變化),但是只有兩類數據,就是支出和收入(結構不變)。
代碼
見參考網頁
空對象(Null)
意圖
使用什么都不做的空對象來代替 NULL。
一個方法返回 NULL,意味著方法的調用端需要去檢查返回值是否是 NULL,這么做會導致非常多的冗余的檢查代碼。并且如果某一個調用端忘記了做這個檢查返回值,而直接使用返回的對象,那么就有可能拋出空指針異常。
結構型
適配器(Adapter)
https://www.jianshu.com/p/93821721bf08
定義
客戶類調用適配器的方法時,在適配器類的內部將調用適配者類的方法,而這個過程對客戶類是透明的,客戶類并不直接訪問適配者類。因此,適配器可以使由于接口不兼容而不能交互的類可以一起工作。這就是適配器模式的模式動機。
角色
-
目標(Target)角色:這就是所期待得到的接口。注意:由于這里討論的是類適配器模式,因此目標不可以是類。
-
源(Adapee)角色:現在需要適配的接口。
-
適配器(Adaper)角色:適配器類是本模式的核心。適配器把源接口轉換成目標接口。顯然,這一角色不可以是接口,而必須是具體類。
類適配器
創建新類,繼承源類,并實現新接口
class adapter extends oldClass implements newFunc{}對象適配器
創建新類持源類的實例,并實現新接口
class adapter implements newFunc { private oldClass oldInstance ;}-
類適配器使用對象繼承的方式,是靜態的定義方式
-
而對象適配器使用對象組合的方式,是動態組合的方式。
接口適配器
創建新的抽象類實現舊接口方法
abstract class adapter implements oldClassFunc { void newFunc();}總結
建議盡量使用對象適配器的實現方式,多用合成/聚合、少用繼承。當然,具體問題具體分析,根據需要來選用實現方式,最適合的才是最好的。
優點
- 更好的復用性
- 更好的擴展性
缺點
過多的使用適配器,會讓系統非常零亂,不易整體進行把握。比如,明明看到調用的是A接口,其實內部被適配成了B接口的實現,一個系統如果太多出現這種情況,無異于一場災難。因此如果不是很有必要,可以不使用適配器,而是直接對系統進行重構。
裝飾模式(Decorator)
給一類對象增加新的功能,裝飾方法與具體的內部邏輯無關。
實現
設計不同種類的飲料,飲料可以添加配料,比如可以添加牛奶,并且支持動態添加新配料。每增加一種配料,該飲料的價格就會增加,要求計算一種飲料的價格。
下圖表示在 DarkRoast 飲料上新增新添加 Mocha 配料,之后又添加了 Whip 配料。DarkRoast 被 Mocha 包裹,Mocha 又被 Whip 包裹。它們都繼承自相同父類,都有 cost() 方法,外層類的 cost() 方法調用了內層類的 cost() 方法。
代碼
interface Source{ void method();} public class Decorator implements Source{private Source source ;public void decotate1(){System.out.println("decorate");}@Overridepublic void method() {decotate1();source.method();} }裝飾模式與代理模式的區別
裝飾器模式關注于在一個對象上動態的添加方法,然而代理模式關注于控制對對象的訪問。
-
用代理模式,代理類(proxy class)可以對它的客戶隱藏一個對象的具體信息。因此,當使用代理模式的時候,我們常常在一個代理類中創建一個對象的實例。
-
當我們使用裝飾器模 式的時候,我們通常的做法是將原始對象作為一個參數傳給裝飾者的構造器。
代理模式(Proxy)
詳細代碼實例:https://www.cnblogs.com/daniels/p/8242592.html
簡介
代理模式的定義:代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。
通俗的來講代理模式就是我們生活中常見的中介。
為什么要用代理模式
-
中介隔離作用
在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現相同的接口。
-
開閉原則,增加功能
真正的業務功能還是由委托類來實現,但是可以在業務功能執行的前后加入一些公共的服務。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委托類。
有哪幾種代理模式
靜態代理
是由程序員創建或特定工具自動生成源代碼,在對其編譯。在程序員運行之前,代理類.class文件就已經被創建了。
代碼:靜態代理創建代理類:
package main.java.proxy.impl;import main.java.proxy.BuyHouse;public class BuyHouseProxy implements BuyHouse {private BuyHouse buyHouse;public BuyHouseProxy(final BuyHouse buyHouse) {this.buyHouse = buyHouse;}@Overridepublic void buyHosue() {System.out.println("買房前準備");buyHouse.buyHosue();System.out.println("買房后裝修");} }靜態代理總結
-
優點:可以做到在符合開閉原則的情況下對目標對象進行功能擴展。
-
缺點:我們得為每一個服務都得創建代理類,工作量太大,不易管理。同時接口一旦發生改變,代理類也得相應修改。
動態代理:JDK反射機制(接口代理)
-
是在程序運行時通過反射機制動態創建的。
-
為需要攔截的接口生成代理對象以實現接口方法攔截功能。
代碼:編寫動態處理器
package main.java.proxy.impl;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method;public class DynamicProxyHandler implements InvocationHandler {private Object object;public DynamicProxyHandler(final Object object) {this.object = object;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("買房前準備");Object result = method.invoke(object, args);System.out.println("買房后裝修");return result;} }代碼:編寫測試類
package main.java.proxy.test;import main.java.proxy.BuyHouse; import main.java.proxy.impl.BuyHouseImpl; import main.java.proxy.impl.DynamicProxyHandler;import java.lang.reflect.Proxy;public class DynamicProxyTest {public static void main(String[] args) {BuyHouse buyHouse = new BuyHouseImpl();BuyHouse proxyBuyHouse = (BuyHouse) Proxy.newProxyInstance(BuyHouse.class.getClassLoader(), newClass[]{BuyHouse.class}, new DynamicProxyHandler(buyHouse));proxyBuyHouse.buyHosue();} }動態代理總結
-
優勢:雖然相對于靜態代理,動態代理大大減少了我們的開發任務,同時減少了對業務接口的依賴,降低了耦合度。
-
劣勢:只能對接口進行代理
動態代理:CGLIB代理
-
其原理是通過字節碼技術為一個類創建子類,并在子類中采用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。
-
但因為采用的是繼承,所以不能對final修飾的類進行代理。
-
JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。
代碼見網頁
CGLIB代理總結:(與JDK代理區別:)
CGLIB創建的動態代理對象比JDK創建的動態代理對象的性能更高,但是CGLIB創建代理對象時所花費的時間卻比JDK多得多。
- 所以對于單例的對象,因為無需頻繁創建對象,用CGLIB合適,反之使用JDK方式要更為合適一些。
- 同時由于CGLib由于是采用動態創建子類的方法,對于final修飾的方法無法進行代理。
外觀模式/門面模式(Facade)
https://www.cnblogs.com/lthIU/p/5860607.html
簡單來說,該模式就是把一些復雜的流程封裝成一個接口供給外部用戶更簡單的使用。這個模式中,涉及到3個角色。
角色
1)門面角色:外觀模式的核心。它被客戶角色調用,它熟悉子系統的功能。內部根據客戶角色的需求預定了幾種功能的組合。
2)子系統角色: 實現了子系統的功能。它對客戶角色和Facade時未知的。它內部可以有系統內的相互交互,也可以由供外界調用的接口。
3)客戶角色: 通過調用Facede來完成要實現的功能。
代碼
package com.huawei.facadeDesign.facade;import org.apache.log4j.Logger;import com.huawei.facadeDesign.children.CPU; import com.huawei.facadeDesign.children.Disk; import com.huawei.facadeDesign.children.Memory;/*** 門面類(核心)* @author Administrator**/ public class Computer {public static final Logger LOGGER = Logger.getLogger(Computer.class);private CPU cpu;private Memory memory;private Disk disk;public Computer(){cpu = new CPU();memory = new Memory();disk = new Disk();}public void start(){LOGGER.info("Computer start begin");cpu.start();disk.start();memory.start();LOGGER.info("Computer start end");}public void shutDown(){LOGGER.info("Computer shutDown begin");cpu.shutDown();disk.shutDown();memory.shutDown();LOGGER.info("Computer shutDown end...");} }優點
-
松散耦合:使得客戶端和子系統之間解耦,讓子系統內部的模塊功能更容易擴展和維護;
-
簡單易用:客戶端根本不需要知道子系統內部的實現,或者根本不需要知道子系統內部的構成,它只需要跟Facade類交互即可。
-
更好的劃分訪問層次:有些方法是對系統外的,有些方法是系統內部相互交互的使用的。子系統把那些暴露給外部的功能集中到門面中,這樣就可以實現客戶端的使用,很好的隱藏了子系統內部的細節。
橋接模式(Bridge Pattern)
http://www.cnblogs.com/houleixx/archive/2008/02/23/1078877.html
含義
在軟件系統中,某些類型由于自身的邏輯,它具有兩個或多個維度的變化,那么如何應對這種“多維度的變化”?
如何利用面向對象的技術來使得該類型能夠輕松的沿著多個方向進行變化,而又不引入額外的復雜度?這就要使用Bridge模式。
由上圖變為下圖
代碼
詳細代碼見參考網頁
static void Main(string[] args){//男人開著公共汽車在高速公路上行駛;Console.WriteLine("=========================");AbstractRoad Road3 = new SpeedWay();Road3.Car = new Bus();people p = new Man();p.Road = Road3;p.Run();Console.Read(); }組合模式(Composite)
https://www.cnblogs.com/lfxiao/p/6816026.html
組合模式是為了表示那些層次結構,同時部分和整體也可能是一樣的結構,常見的如文件夾或者樹.
定義
組合模式定義了如何將容器對象和葉子對象進行遞歸組合,使得客戶在使用的過程中無須進行區分,可以對他們進行一致的處理。
在使用組合模式中需要注意一點也是組合模式最關鍵的地方:葉子對象和組合對象實現相同的接口。這就是組合模式能夠將葉子節點和對象節點進行一致處理的原因。
角色
1.Component :組合中的對象聲明接口,在適當的情況下,實現所有類共有接口的默認行為。聲明一個接口用于訪問和管理Component子部件。
2.Leaf:葉子對象。葉子結點沒有子結點。
3.Composite:容器對象,定義有枝節點行為,用來存儲子部件,在Component接口中實現與子部件有關操作,如增加(add)和刪除(remove)等。
適用場景
1、需要表示一個對象整體或部分層次,在具有整體和部分的層次結構中,希望通過一種方式忽略整體與部分的差異,可以一致地對待它們。
2、讓客戶能夠忽略不同對象層次的變化,客戶端可以針對抽象構件編程,無須關心對象層次結構的細節。
享元模式(Flyweight)
https://www.cnblogs.com/chenssy/p/3330555.html
定義
所謂享元模式就是運行共享技術有效地支持大量細粒度對象的復用,所以享元模式要求能夠共享的對象必須是細粒度對象。
-
內部狀態:在享元對象內部不隨外界環境改變而改變的共享部分。
-
外部狀態:隨著環境的改變而改變,不能夠共享的狀態就是外部狀態。
代碼
享元工廠類FlyweightFactory:
利用了HashMap保存已經創建的顏色
public class FlyweightFactory{static Map<String, Shape> shapes = new HashMap<String, Shape>();public static Shape getShape(String key){Shape shape = shapes.get(key);//如果shape==null,表示不存在,則新建,并且保持到共享池中if(shape == null){shape = new Circle(key);shapes.put(key, shape);}return shape;}public static int getSum(){return shapes.size();} }客戶端程序:Client.java
public class Client {public static void main(String[] args) {Shape shape1 = FlyweightFactory.getShape("紅色");shape1.draw();Shape shape2 = FlyweightFactory.getShape("灰色");shape2.draw();Shape shape3 = FlyweightFactory.getShape("綠色");shape3.draw();Shape shape4 = FlyweightFactory.getShape("紅色");shape4.draw();Shape shape5 = FlyweightFactory.getShape("灰色");shape5.draw();Shape shape6 = FlyweightFactory.getShape("灰色");shape6.draw();System.out.println("一共繪制了"+FlyweightFactory.getSum()+"中顏色的圓形");} }參考
簡書大牛:https://www.jianshu.com/nb/10186551
Github:https://github.com/CyC2018/Interview-Notebook/blob/master/notes/設計模式.md
菜鳥網:http://www.runoob.com/design-pattern/design-pattern-tutorial.html
補充:
23種設計模式總結:
https://www.cnblogs.com/tongkey/p/7170826.html
https://www.cnblogs.com/malihe/p/6891920.html
總結
以上是生活随笔為你收集整理的快速梳理23种常用的设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分享13:2021元旦之《白夜行》
- 下一篇: 简易绘图板