Java內功心法,建立型設計模式包括哪些

1. 單例(Singleton)

Intent

確保一個類只有一個實例,並提供該實例的全局訪問點。html

Class Diagram

使用一個私有構造函數、一個私有靜態變量以及一個公有靜態函數來實現。java

私有構造函數保證了不能經過構造函數來建立對象實例,只能經過公有靜態函數返回惟一的私有靜態變量。git

Implementation

Ⅰ 懶漢式-線程不安全

如下實現中,私有靜態變量 uniqueInstance 被延遲實例化,這樣作的好處是,若是沒有用到該類,那麼就不會實例化 uniqueInstance,從而節約資源。github

這個實如今多線程環境下是不安全的,若是多個線程可以同時進入 if (uniqueInstance == null) ,而且此時 uniqueInstance 爲 null,那麼會有多個線程執行 uniqueInstance = new Singleton(); 語句,這將致使實例化屢次 uniqueInstance。apache

public class Singleton {

    private static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}複製代碼

Ⅱ 餓漢式-線程安全

線程不安全問題主要是因爲 uniqueInstance 被實例化屢次,採起直接實例化 uniqueInstance 的方式就不會產生線程不安全問題。編程

可是直接實例化的方式也丟失了延遲實例化帶來的節約資源的好處。api

public class Singleton {
    //線程不安全問題主要是因爲 uniqueIntance被實例化了屢次,
    //若是uniqueInstance採用直接實例化的話,就不會被實例化屢次,也就不會產生線程不安全的問題。
    private static Singleton uniqueInstance=new Singleton2();

    private Singleton(){

    }

    public  static Singleton getUniqueInstance(){
        return uniqueInstance;
    }
}複製代碼

Ⅲ 懶漢式-線程安全

只須要對 getUniqueInstance() 方法加鎖,那麼在一個時間點只能有一個線程可以進入該方法, 從而避免了實例化屢次 uniqueInstance。緩存

可是當一個線程進入該方法以後,其它試圖進入該方法的線程都必須等待, 即便 uniqueInstance 已經被實例化了。這會讓線程阻塞時間過長,所以該方法有性能問題,不推薦使用。安全

public class Singleton{
    //線程不安全問題主要是因爲 uniqueIntance被實例化了屢次,
    //若是uniqueInstance採用直接實例化的話,就不會被實例化屢次,也就不會產生線程不安全的問題。
    private static Singleton uniqueInstance;

    private Singleton(){

    }

    //當一個線程進入該方法以後,其它試圖進入該方法的線程都必須等待
    public  synchronized static Singleton getUniqueInstance(){
        if(uniqueInstance == null){
           uniqueInstance = new Singleton();
        }
        return uniqueInstance;
    }
}複製代碼
  • 延遲加載的思想 就是一開始不要加載資源或者數據,一直等,等到立刻就要使用這個資源或者數據了, 躲不過去了才加載,因此也稱Lazy Load, 不是懶惰啊,是「延遲加載」,這在實際開發中是一種很常見的思想,儘量的節約資源。
  • 緩存的思想 把這些數據緩存到內存裏面,每次操做的時候,先到內存裏面找,看有沒有這些數據, 若是有,那麼就直接使用,若是沒有那麼就獲取它,並設置到緩存中,下一次訪問的時候就能夠直接從內存中獲取了。 從而節省大量的時間,固然,緩存是一種典型的空間換時間的方案。

Ⅳ 雙重校驗鎖-線程安全

uniqueInstance 只須要被實例化一次,以後就能夠直接使用了。加鎖操做只須要對實例化那部分的代碼進行,只有當 uniqueInstance 沒有被實例化時,才須要進行加鎖。bash

雙重校驗鎖先判斷 uniqueInstance 是否已經被實例化,若是沒有被實例化,那麼纔對實例化語句進行加鎖。

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public static Singleton getUniqueInstance() {
        if (uniqueInstance == null) {
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}複製代碼

考慮下面的實現,也就是隻使用了一個 if 語句。 在 uniqueInstance == null 的狀況下,若是兩個線程都執行了 if 語句,那麼兩個線程都會進入 if 語句塊內。雖然在 if 語句塊內有加鎖操做,可是兩個線程都會執行 uniqueInstance = new Singleton(); 這條語句,只是前後的問題,那麼就會進行兩次實例化。所以必須使用雙重校驗鎖,也就是須要使用兩個 if 語句。

if (uniqueInstance == null) {
    synchronized (Singleton.class) {
        uniqueInstance = new Singleton();
    }
}複製代碼

uniqueInstance 採用 volatile 關鍵字修飾也是頗有必要的, uniqueInstance = new Singleton(); 這段代碼實際上是分爲三步執行:

  1. 爲 uniqueInstance 分配內存空間
  2. 初始化 uniqueInstance
  3. 將 uniqueInstance 指向分配的內存地址

可是因爲 JVM 具備指令重排的特性,執行順序有可能變成 1>3>2。指令重排在單線程環境下不會出現問題,可是在多線程環境下會致使一個線程得到尚未初始化的實例。 例如,線程 T1 執行了 1 和 3,此時 T2 調用 getUniqueInstance() 後發現 uniqueInstance 不爲空,所以返回 uniqueInstance,但此時 uniqueInstance 還未被初始化。

使用 volatile 能夠禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。

Ⅴ 靜態內部類實現

當 Singleton 類加載時,靜態內部類 SingletonHolder 沒有被加載進內存。只有當調用 getUniqueInstance() 方法從而觸發 SingletonHolder.INSTANCE 時 SingletonHolder 纔會被加載,此時初始化 INSTANCE 實例,而且 JVM 能確保 INSTANCE 只被實例化一次。

這種方式不只具備延遲初始化的好處,並且由 JVM 提供了對線程安全的支持。

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 {

    INSTANCE;

    private String objName;


    public String getObjName() {
        return objName;
    }


    public void setObjName(String objName) {
        this.objName = objName;
    }


    public static void main(String[] args) {

        // 單例測試
        Singleton firstSingleton = Singleton.INSTANCE;
        firstSingleton.setObjName("firstName");
        System.out.println(firstSingleton.getObjName());
        Singleton secondSingleton = Singleton.INSTANCE;
        secondSingleton.setObjName("secondName");
        System.out.println(firstSingleton.getObjName());
        System.out.println(secondSingleton.getObjName());

        // 反射獲取實例測試
        try {
            Singleton[] enumConstants = Singleton.class.getEnumConstants();
            for (Singleton enumConstant : enumConstants) {
                System.out.println(enumConstant.getObjName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
firstName
secondName
secondName
secondName複製代碼

該實如今屢次序列化再進行反序列化以後,不會獲得多個實例。而其它實現須要使用 transient 修飾全部字段, 而且實現序列化和反序列化的方法。

該實現能夠防止反射攻擊。在其它實現中,經過 setAccessible() 方法能夠將私有構造函數的訪問級別設置爲 public,而後調用構造函數從而實例化對象,若是要防止這種攻擊,須要在構造函數中添加防止屢次實例化的代碼。該實現是由 JVM 保證只會實例化一次,所以不會出現上述的反射攻擊。

枚舉實現(最推薦使用)

public class Singleton {
    private Singleton(){}

    public static Singleton getUniqueInstance(){
        return Singleton.INSTANCE.getSingleton();
    }

    private enum Singleton{
        INSTANCE;
        //若是打算自定義本身的方法,那麼必須在enum實例序列的最後添加一個分號。
        //並且 Java 要求必須先定義 enum 實例
        private Singleton singleton;

        //JVM保證這個方法絕對只被調用一次
        Singleton(){
            singleton=new Singleton();
        }

        public Singleton getSingleton() {
            return singleton;
        }
    }
}複製代碼

Examples

  • Logger Classes
  • Configuration Classes
  • Accesing resources in shared mode
  • Factories implemented as Singletons

JDK

2. 簡單工廠(Simple Factory)

Intent

在建立一個對象時不向客戶暴露內部細節,並提供一個建立對象的通用接口。

Class Diagram

簡單工廠把實例化的操做單獨放到一個類中,這個類就成爲簡單工廠類, 讓簡單工廠類來決定應該用哪一個具體子類來實例化。

這樣作能把客戶類和具體子類的實現解耦, 客戶類再也不須要知道有哪些子類以及應當實例化哪一個子類。 客戶類每每有多個,若是不使用簡單工廠,那麼全部的客戶類都要知道全部子類的細節。 並且一旦子類發生改變,例如增長子類,那麼全部的客戶類都要進行修改。

Implementation

public interface Product {
}
public class ConcreteProduct implements Product {
}
public class ConcreteProduct1 implements Product {
}
public class ConcreteProduct2 implements Product {
}複製代碼

如下的 Client 類包含了實例化的代碼,這是一種錯誤的實現。若是在客戶類中存在這種實例化代碼,就須要考慮將代碼放到簡單工廠中。

public class Client {

    public static void main(String[] args) {
        int type = 1;
        Product product;
        if (type == 1) {
            product = new ConcreteProduct1();
        } else if (type == 2) {
            product = new ConcreteProduct2();
        } else {
            product = new ConcreteProduct();
        }
        // do something with the product
    }
}複製代碼

如下的 SimpleFactory 是簡單工廠實現,它被全部須要進行實例化的客戶類調用。

public class SimpleFactory {

    public Product createProduct(int type) {
        if (type == 1) {
            return new ConcreteProduct1();
        } else if (type == 2) {
            return new ConcreteProduct2();
        }
        return new ConcreteProduct();
    }
}
public class Client {

    public static void main(String[] args) {
        SimpleFactory simpleFactory = new SimpleFactory();
        Product product = simpleFactory.createProduct(1);
        // do something with the product
    }
}複製代碼
  • 簡單工廠的優缺點
幫助封裝:簡單工廠雖然很簡單,可是很是友好的幫助咱們實現了組件的封裝,而後讓組件外部能真正面向接口編程。
解耦:經過簡單工廠,把客戶類和具體子類的實現解耦。
可能增長客戶端的複雜度: 若是經過客戶端的參數來選擇具體的實現類, 那麼就必須讓客戶端能理解各個參數所表明的具體功能和含義,這會增長客戶端使用的難度, 也 部分暴露了內部實現,這種狀況能夠選用可配置的方式來實現
不方便擴展子工廠:私有化簡單工廠的構造方法,使用靜態方法來建立接口, 也就不能經過寫簡單工廠類的子類來改變建立接口的方法的行爲了。不過,一般狀況下是不須要爲簡單工廠建立子類的。

3. 工廠方法(Factory Method)

Intent

定義了一個建立對象的接口,但由子類決定要實例化哪一個類。工廠方法把實例化操做推遲到子類。

Class Diagram

在簡單工廠中,建立對象的是另外一個類,而在工廠方法中,是由子類來建立對象。

下圖中,Factory 有一個 doSomething() 方法,這個方法須要用到一個產品對象,這個產品對象由 factoryMethod() 方法建立。該方法是抽象的,須要由子類去實現。

Implementation

public abstract class Factory {
    abstract public Product factoryMethod();
    public void doSomething() {
        Product product = factoryMethod();
        // do something with the product
    }
}
public class ConcreteFactory extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct();
    }
}
public class ConcreteFactory1 extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct1();
    }
}
public class ConcreteFactory2 extends Factory {
    public Product factoryMethod() {
        return new ConcreteProduct2();
    }
}複製代碼

JDK

4. 抽象工廠(Abstract Factory)

Intent

提供一個接口,用於建立 相關的對象家族

Class Diagram

抽象工廠模式建立的是對象家族,也就是不少對象而不是一個對象,而且這些對象是相關的,也就是說必須一塊兒建立出來。而工廠方法模式只是用於建立一個對象,這和抽象工廠模式有很大不一樣。

抽象工廠模式用到了工廠方法模式來建立單一對象,AbstractFactory 中的 createProductA() 和 createProductB() 方法都是讓子類來實現,這兩個方法單獨來看就是在建立一個對象,這符合工廠方法模式的定義。

至於建立對象的家族這一律念是在 Client 體現,Client 要經過 AbstractFactory 同時調用兩個方法來建立出兩個對象,在這裏這兩個對象就有很大的相關性,Client 須要同時建立出這兩個對象。

從高層次來看,抽象工廠使用了組合,即 Cilent 組合了 AbstractFactory,而工廠方法模式使用了繼承。

Implementation

public class AbstractProductA {
}
public class AbstractProductB {
}
public class ProductA1 extends AbstractProductA {
}
public class ProductA2 extends AbstractProductA {
}
public class ProductB1 extends AbstractProductB {
}
public class ProductB2 extends AbstractProductB {
}
public abstract class AbstractFactory {
    abstract AbstractProductA createProductA();
    abstract AbstractProductB createProductB();
}
public class ConcreteFactory1 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA1();
    }

    AbstractProductB createProductB() {
        return new ProductB1();
    }
}
public class ConcreteFactory2 extends AbstractFactory {
    AbstractProductA createProductA() {
        return new ProductA2();
    }

    AbstractProductB createProductB() {
        return new ProductB2();
    }
}
public class Client {
    public static void main(String[] args) {
        AbstractFactory abstractFactory = new ConcreteFactory1();
        AbstractProductA productA = abstractFactory.createProductA();
        AbstractProductB productB = abstractFactory.createProductB();
        // do something with productA and productB
    }
}複製代碼

JDK

5. 生成器(Builder)

Intent

封裝一個對象的構造過程,並容許按步驟構造。 (將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。)

Class Diagram

要實現一樣的構建過程能夠建立不一樣的表現,那麼一個天然的思路就是 先把構建過程獨立出來,在生成器模式中把它稱爲指導者, 由它來指導裝配過程,可是不負責每步具體的實現。 固然,光有指導者是不夠的,必需要有能具體實現每步的對象,在生成器模式中稱這些實現對象爲生成器。 這樣一來,指導者就是能夠重用的構建過程,而生成器是能夠被切換的具體實現

Implementation1

**
 * 指導者負責指導裝配過程,可是不負責每步具體的實現。
 */
public class Director {

    private AbstractComputerBuilder computerBuilder;

    public void setComputerBuilder(AbstractComputerBuilder computerBuilder) {
        this.computerBuilder = computerBuilder;
    }

    public Product getProduct() {
        return computerBuilder.getProduct();
    }

    public void constructComputer() {
        computerBuilder.buildProduct();
        computerBuilder.buildMaster();
        computerBuilder.buildScreen();
        computerBuilder.buildKeyboard();
        computerBuilder.buildMouse();
        computerBuilder.buildAudio();
    }
}
/**
 * 定義一個產品類
 */
public class Product {
    private String master;
    private String screen;
    private String keyboard;
    private String mouse;
    private String audio;
    
    public void setMaster(String master) {
        this.master = master;
    }
    

    public void setScreen(String screen) {
        this.screen = screen;
    }

    public String getMaster() {
        return master;
    }

    public String getScreen() {
        return screen;
    }

    public String getKeyboard() {
        return keyboard;
    }

    public void setKeyboard(String keyboard) {
        this.keyboard = keyboard;
    }

    public String getMouse() {
        return mouse;
    }

    public void setMouse(String mouse) {
        this.mouse = mouse;
    }

    public String getAudio() {
        return audio;
    }

    public void setAudio(String audio) {
        this.audio = audio;
    }
}
/**
 * 生成器的抽象類
 * 負責具體實現每步的對象
 */
public abstract class AbstractComputerBuilder {
    protected Product product;

    public Product getProduct() {
        return product;
    }

    public void buildProduct(){
        product=new Product();
        System.out.println("生產出一臺電腦");
    }

    public abstract void buildMaster();
    public abstract void buildScreen();
    public abstract void buildKeyboard();
    public abstract void buildMouse();
    public abstract void buildAudio();
}
public class HPComputerBuilder extends AbstractComputerBuilder{
    @Override
    public void buildMaster() {
        // TODO Auto-generated method stub
        product.setMaster("i7,16g,512SSD,1060");
        System.out.println("(i7,16g,512SSD,1060)的惠普主機");
    }

    @Override
    public void buildScreen() {
        // TODO Auto-generated method stub
        product.setScreen("4K");
        System.out.println("(4K)的惠普顯示屏");
    }

    @Override
    public void buildKeyboard() {
        // TODO Auto-generated method stub
        product.setKeyboard("cherry 青軸機械鍵盤");
        System.out.println("(cherry 青軸機械鍵盤)的鍵盤");
    }

    @Override
    public void buildMouse() {
        // TODO Auto-generated method stub
        product.setMouse("MI 鼠標");
        System.out.println("(MI 鼠標)的鼠標");
    }

    @Override
    public void buildAudio() {
        // TODO Auto-generated method stub
        product.setAudio("飛利浦 音響");
        System.out.println("(飛利浦 音響)的音響");
    }
}
public class DELLComputerBuilder extends AbstractComputerBuilder{
    @Override
    public void buildMaster() {
        // TODO Auto-generated method stub
        product.setMaster("i7,32g,1TSSD,1060");
        System.out.println("(i7,32g,1TSSD,1060)的戴爾主機");
    }

    @Override
    public void buildScreen() {
        // TODO Auto-generated method stub
        product.setScreen("4k");
        System.out.println("(4k)的dell顯示屏");
    }

    @Override
    public void buildKeyboard() {
        // TODO Auto-generated method stub
        product.setKeyboard("cherry 黑軸機械鍵盤");
        System.out.println("(cherry 黑軸機械鍵盤)的鍵盤");
    }

    @Override
    public void buildMouse() {
        // TODO Auto-generated method stub
        product.setMouse("MI 鼠標");
        System.out.println("(MI 鼠標)的鼠標");
    }

    @Override
    public void buildAudio() {
        // TODO Auto-generated method stub
        product.setAudio("飛利浦 音響");
        System.out.println("(飛利浦 音響)的音響");
    }
}
/**
 * 指導者就是能夠重用的構建過程,
 * 而生成器是能夠被切換的具體實現
 */
public class Client {
    public static void main(String[] args) {
        AbstractComputerBuilder computerBuilder=new HPComputerBuilder();
        AbstractComputerBuilder computerBuilder2=new DELLComputerBuilder();
        Director director=new Director();

        director.setComputerBuilder(computerBuilder);
        director.constructComputer();
        //獲取PC
        Product pc=director.getProduct();

        director.setComputerBuilder(computerBuilder2);
        director.constructComputer();
        Product pc2=director.getProduct();
    }
}複製代碼

生成器的調用順序:

Implementation2

如下是一個簡易的 StringBuilder 實現,參考了 JDK 1.8 源碼。

public class AbstractStringBuilder {
    protected char[] value;

    protected int count;

    public AbstractStringBuilder(int capacity) {
        count = 0;
        value = new char[capacity];
    }

    public AbstractStringBuilder append(char c) {
        ensureCapacityInternal(count + 1);
        value[count++] = c;
        return this;
    }

    private void ensureCapacityInternal(int minimumCapacity) {
        // overflow-conscious code
        if (minimumCapacity - value.length > 0)
            expandCapacity(minimumCapacity);
    }

    void expandCapacity(int minimumCapacity) {
        int newCapacity = value.length * 2 + 2;
        if (newCapacity - minimumCapacity < 0)
            newCapacity = minimumCapacity;
        if (newCapacity < 0) {
            if (minimumCapacity < 0) // overflow
                throw new OutOfMemoryError();
            newCapacity = Integer.MAX_VALUE;
        }
        value = Arrays.copyOf(value, newCapacity);
    }
}
public class StringBuilder extends AbstractStringBuilder {
    public StringBuilder() {
        super(16);
    }

    @Override
    public String toString() {
        // Create a copy, don't share the array return new String(value, 0, count); } } public class Client { public static void main(String[] args) { StringBuilder sb = new StringBuilder(); final int count = 26; for (int i = 0; i < count; i++) { sb.append((char) ('a' + i)); } System.out.println(sb.toString()); } } abcdefghijklmnopqrstuvwxyz複製代碼

JDK

6. 原型模式(Prototype)

Intent

使用原型實例指定要建立對象的類型,經過複製這個原型來建立新對象

Class Diagram



Implementation

public abstract class Prototype {
    abstract Prototype myClone();
}
public class ConcretePrototype extends Prototype {

    private String filed;

    public ConcretePrototype(String filed) {
        this.filed = filed;
    }

    @Override
    Prototype myClone() {
        return new ConcretePrototype(filed);
    }

    @Override
    public String toString() {
        return filed;
    }
}
public class Client {
    public static void main(String[] args) {
        Prototype prototype = new ConcretePrototype("abc");
        Prototype clone = prototype.myClone();
        System.out.println(clone.toString());
    }
}
複製代碼


abc複製代碼
相關文章
相關標籤/搜索