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

前言

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

分爲三篇文章:java

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

面試知識點複習手冊

全複習手冊文章導航git

點擊公衆號下方:技術推文——面試衝刺github

快速回憶

建立型面試

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

理念

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

高內聚低耦合

高內聚:系統中A、B兩個模塊進行交互,若是修改了A模塊,不影響模塊B的工做,那麼認爲A有足夠的內聚。sql

低耦合:就是A模塊與B模塊存在依賴關係,那麼當B發生改變時,A模塊仍然能夠正常工做,那麼就認爲A與B是低耦合的。apache

建立型

單例模式

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

同時參考:api

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

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

相關文章
相關標籤/搜索