快速梳理常用的設計模式(上篇)

qqxx6661發表於2019-02-26

前言

本文旨在快速梳理常用的設計模式,瞭解每個模式主要針對的是哪些情況以及其基礎特徵,每個模式前都有列舉出一個或多個可以深入閱讀的參考網頁,以供讀者詳細瞭解其實現。

分為三篇文章:

  • 上篇:設計模式基礎理念和建立型設計模式
  • 中篇:行為型設計模式
  • 下篇:結構型設計模式

面試知識點複習手冊

全複習手冊文章導航

點選公眾號下方:技術推文——面試衝刺

快速回憶

建立型

  • 單例(Singleton)
  • 工廠模式
    • 簡單工廠(Simple Factory)
    • 工廠方法(Factory Method)
    • 抽象工廠(Abstract Factory)
  • 生成器(Builder)
  • 原型模式(Prototype)

理念

首先搞清楚一點,設計模式不是高深技術,不是奇淫技巧。設計模式只是一種設計思想,針對不同的業務場景,用不同的方式去設計程式碼結構,其最最本質的目的是為了解耦,延伸一點的話,還有為了可擴充套件性和健壯性,但是這都是建立在解耦的基礎之上。

高內聚低耦合

高內聚:系統中A、B兩個模組進行互動,如果修改了A模組,不影響模組B的工作,那麼認為A有足夠的內聚。

快速梳理常用的設計模式(上篇)

低耦合:就是A模組與B模組存在依賴關係,那麼當B發生改變時,A模組仍然可以正常工作,那麼就認為A與B是低耦合的。

快速梳理常用的設計模式(上篇)

建立型

單例模式

請參考Github詳細解釋,下面的一點僅供快速複習。Github寫的很好。

同時參考:

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)

www.jianshu.com/p/d1b6731c1…

定義

在建立一個物件時不向客戶暴露內部細節,並提供一個建立物件的通用介面。

在簡單工廠模式中,可以根據引數的不同返回不同類的例項

簡單工廠模式專門定義一個類來負責建立其他類的例項

結構

簡單工廠模式包含如下角色:

  • 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加密技術 獲取不同加密演算法的金鑰生成器:

KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
複製程式碼

建立密碼器:

Cipher cp = Cipher.getInstance("DESede");
複製程式碼

工廠方法(Factory Method)

www.jianshu.com/p/1cf9859e0…

意圖

又稱為工廠模式/虛擬構造器(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中的工廠方法:
Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password=");
Statement statement=conn.createStatement();
ResultSet rs=statement.executeQuery("select * from UserInfo");
複製程式碼

抽象工廠(Abstract Factory)

www.jianshu.com/p/d6622f3e7…

定義

產品等級結構 :產品等級結構即產品的繼承結構,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、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和一臺Tab
              Factory factory = new Factory_Pad();
              Apple apple = factory.createAppleProduct();
              apple.AppleStyle();
              Sumsung sumsung = factory.createSumsungProduct();
              sumsung.BangziStyle();
  
             //採購商又要一臺iPhone和一臺Note2
            factory = new Factory_Phone();
             apple = factory.createAppleProduct();
             apple.AppleStyle();
             sumsung = factory.createSumsungProduct();
             sumsung.BangziStyle();
         }
複製程式碼

優缺點

優點

①應用抽象工廠模式可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛的應用。

增加新的具體工廠和產品族很方便,因為一個具體的工廠實現代表的是一個產品族,無須修改已有系統,符合“開閉原則”。

缺點

開閉原則的傾斜性(增加新的工廠和產品族容易,增加新的產品等級結構麻煩

適用環境

在以下情況下可以使用抽象工廠模式:

①一個系統不應當依賴於產品類例項如何被建立、組合和表達的細節,這對於所有型別的工廠模式都是重要的。

②系統中有多於一個的產品族,而每次只使用其中某一產品族。(與工廠方法模式的區別)

③屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。

④系統提供一個產品類的庫,所有的產品以同樣的介面出現,從而使客戶端不依賴於具體實現。

JDK

生成器(Builder)

blog.csdn.net/c275046758/…

意圖

封裝一個物件的構造過程,並允許按步驟構造。

實現

實現程式碼見參考連結

一水杯工廠要生產各式各樣的水杯,無論杯子是神馬造型,但都包括繩子,帽子和杯體。以此模型建立各種型別的杯子。

快速梳理常用的設計模式(上篇)

JDK

原型模式(Prototype)

www.cnblogs.com/lwbqqyumidi…

意圖

通過一個已經存在的物件,複製出更多的具有與此物件具有相同型別的新的物件。

淺複製 clone():

我們知道,一個類的定義中包括屬性和方法。屬性用於表示物件的狀態,方法用於表示物件所具有的行為。其中,屬性既可以是Java中基本資料型別,也可以是引用型別。

Java中的淺複製通常使用 clone() 方式完成。

當進淺複製時,clone函式返回的是一個引用,指向的是新的clone出來的物件,此物件與原物件分別佔用不同的堆空間。同時,複製出來的物件具有與原物件一致的狀態。

此處物件一致的狀態是指:複製出的物件與原物件中的屬性值完全相等==。

深複製 deepclone():

Java中的深複製一般是通過物件的序列化和反序列化得以實現。序列化時,需要實現Serializable介面。

從輸出結果中可以看出,深複製不僅在堆記憶體上開闢了空間以儲存複製出的物件,甚至連物件中的引用型別的屬性所指向的物件也得以複製,重新開闢了堆空間儲存。

JDK

-----正文結束-----

更多精彩文章,請查閱我的部落格或關注我的公眾號:Rude3Knife

全複習手冊文章導航

點選公眾號下方:技術推文——面試衝刺

全複習手冊文章導航(CSDN)

知識點複習手冊文章推薦

關注我

我是蠻三刀把刀,目前為後臺開發工程師。主要關注後臺開發,網路安全,Python爬蟲等技術。

來微信和我聊聊:yangzd1102

Github:github.com/qqxx6661

原創部落格主要內容

  • 筆試面試複習知識點手冊
  • Leetcode演算法題解析(前150題)
  • 劍指offer演算法題解析
  • Python爬蟲相關技術分析和實戰
  • 後臺開發相關技術分析和實戰

同步更新以下部落格

1. Csdn

blog.csdn.net/qqxx6661

擁有專欄:Leetcode題解(Java/Python)、Python爬蟲開發、面試助攻手冊

2. 知乎

www.zhihu.com/people/yang…

擁有專欄:碼農面試助攻手冊

3. 掘金

juejin.im/user/5b4801…

4. 簡書

www.jianshu.com/u/b5f225ca2…

個人公眾號:Rude3Knife

個人公眾號:Rude3Knife

如果文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章