創建型模式之單例模式與工廠模式(一) Volatile的應用DCL單例模式(四)

一、基本介紹

  創建型模式,就是創建對象的模式,抽象了實例化的過程。它幫助一個系統獨立於如何創建、組合和表示它的那些對象。關注的時對象的創建,創建型模式將創建對象的過程進行了抽象,也可以理解爲將創建對象的過程進行了封裝,作爲客戶程序僅僅需要去使用對象,而不再關心創建過程中的邏輯。

  具體的創建型模式可分爲:

  • 單例模式(Singleton)
  • 簡單工廠模式(Simple Factory)
  • 工廠方法模式(Factory Method)
  • 抽象工廠模式(Abstract Factory)
  • 原型模式(Prototype)
  • 建造者模式(Builder)

二、單例模式

1,基本介紹

  所謂的單例設計模式,就是採取一定的方法保證在整個的軟件系統中,對某個類只能存在一個對象實例,並且該類只提供一個取得其對象實例的方法。例如:Hibernate的SessionFactory,它充當數據存儲源的代理,並負責創建Session對象。SessionFactory並不是輕量級的,一般情況下,一個項目通常只需要一個SessionFactory就夠,這時就需要使用單例模式。

2,實現方式

  單例設計模式有八種方式(推薦使用1、2、6、7、8):

  1. 餓漢式(靜態常量)
  2. 餓漢式(靜態代碼塊)
  3. 懶漢式(線程不安全)
  4. 懶漢式(線程安全,同步方法)
  5. 懶漢式(線程不安全,同步代碼塊)
  6. 雙重檢查
  7. 靜態內部類
  8. 枚舉

3,餓漢式(靜態常量)

a)應用實例

class Singleton{
    private static final Singleton singleton = new Singleton();
    private Singleton() {
    }
    public static Singleton getInstant() {
        return singleton;
    }
}

b)優缺點

  優點:這種寫法比較簡單,就是在類裝載的時候就完成實例化,避免了線程同步問題

  缺點:在類裝載的時候就完成實例化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個實例,則會造成內存的浪費

  結論:這種單例模式可用,但是可能造成內存浪費

4,餓漢式(靜態代碼塊)

a)應用實例

class Singleton1{
    private static Singleton1 singleton;
    private Singleton1(){}
    static {
        singleton = new Singleton1();
    }
    public static Singleton1 getInstance(){
        return singleton;
    }
}

b)優缺點

  優缺點:這種方式和上面的方式其實類似,只不過將類實例化的過程放在了靜態代碼塊中,也是在類裝載的時候,就執行靜態代碼塊中的代碼,初始化類的實例。優缺點和上面是一樣的。

  結論:這種單例模式可用,但是可能造成內存浪費。

5,懶漢式(線程不安全)

a)應用實例

class Singleton2{
    private static Singleton2 singleton;
    private Singleton2(){}
    public static Singleton2 getSingleton(){
        if (singleton == null) {
            singleton = new Singleton2();
        }
        return singleton;
    }
}

b)優缺點

  • 起到了LazingLoading的效果(使用時才創建),但是隻能在單線程下使用。
  • 如果在多線程下,多個線程同時判斷singleton爲null,會創建多個實例
  • 結論:在實際開發中,不要使用這種方式創建

6,懶漢式(線程安全,同步方法)

a)應用實例

class Singleton3{
    private static Singleton3 singleton;
    private Singleton3(){}
    public static synchronized Singleton3 getSingleton(){
        if (singleton == null) {
            singleton = new Singleton3();
        }
        return singleton;
    }
}

b)優缺點

  • 採用同步方法關鍵字synchronized解決線程安全問題
  • 效率太低了,每個線程在想獲得類的實例時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次實例化代碼就夠了,後面的想獲得該類實例,直接return就行了。
  • 結論:實際開發中,不推薦使用這種方式

7,懶漢式(線程不安全,同步代碼塊)

a)應用實例

class Singleton4{
    private static Singleton4 singleton4;
    private Singleton4(){}
    public static Singleton4 getInstance() {
        if (singleton4 == null) {
            synchronized (Singleton4.class) {
                singleton4 = new Singleton4();
            }
        }
        return singleton4;
    }
}

b)優缺點

  • 這種方式本意是想對6方法進行改進,因爲前面同步方法效率太低了,改爲同步產生實例化的代碼塊
  • 但是這種同步並不能起到線程同步的作用解決不了線程安全的問題)。因爲同樣如果多個線程進行判斷singleton爲null時,雖然會在synchronized方法外被阻塞,但是阻塞完成之後還是會繼續執行產生多個不同實例對象
  • 結論:在實際開發中,不能使用這種方式

8,雙重檢查

a)應用實例

class Singleton5{
    private static volatile Singleton5 singleton;
    private Singleton5(){}
    public static Singleton5 getSingleton(){
        if (singleton == null) {
            synchronized (Singleton5.class) {
                if (singleton == null) {
                    singleton = new Singleton5();
                }
            }
        }
        return singleton;
    }
}

b)優缺點

  • 第一個if判斷能提升創建效率,如果去掉,多線程會阻塞;
  • 第二個if判斷能解決線程安全問題,如果去掉會有多個線程進入synchronized代碼塊中創建;
  • synchronized方法能保順序執行,如果去掉也會創建多個實例;
  • volatile關鍵字能保證可見性和禁止指令重排,詳細點擊Volatile的應用DCL單例模式(四)
  • 結論:實現了線程安全;延遲加載;效率較高。推薦使用

9,靜態內部類

a)應用實例

class Singleton6 {
    private Singleton6(){}
    private static class StaticClass {
        private static final Singleton6 INSTANCE = new Singleton6();
    }
    public static Singleton6 getInstance() {
        return StaticClass.INSTANCE;
    }
}

b)分析

  • 當外部類Singleton進行類轉載時,靜態內部是不會被裝載的
  • 當調用Singleton的getInstance()方法,用到INSTANCE靜態常量時,靜態類纔會被裝載,且只會裝載一次。在裝載時,線程是安全的

c)優缺點

  • 這種方式採用了類裝載的機制來保證初始化實例時只會有個一個線程
  • 靜態內部類方式在Singleton類被裝載時並不會立即實例化,而是需要實例化時,調用getInstance()方式,纔會裝載類StaticClass類,從而完成Singleton的實例化
  • 類的靜態屬性只會在第一次加載類的時候初始化,所以在這裏,JVM幫助我們保證了線程的安全性,在類進行初始化時,別的線程是無法進入的
  • 優點:避免了線程不安全,利用靜態內部類特點實現延遲加載,效率高
  • 結論:推薦使用

10,枚舉

a)應用實例

enum Singleton7{
    INSTANCE;
}

b)優缺點

  • 這是藉助JDK1.5中添加的枚舉來實現單例模式。不僅能避免多線程同步問題,而且還能防止反序列化重新創建新的對象
  • 結論:推薦使用

11,單例模式在JDK中的使用

  Runtime源碼

  

12,注意事項

  • 單例模式保證了系統內存中改類只存在一個對象,節省了系統資源,對於一些需要頻繁創建銷燬的對象,使用單例模式可以提高系統性能
  • 當想實例化一個單例類的時候,必須要記住使用相應的獲取對象的方法,而不是使用new
  • 單例模式使用的場景:需要頻繁創建和銷燬的對象、創建對象時耗時過多或耗費資源過多(即:重量級對象),但又經常用到的對象、工具類對象、頻繁訪問數據庫或文件的對象(比如數據源、session工廠等)

三、工廠模式

需求

  • 1) 披薩的種類很多(比如 GreekPizza、CheesePizza 等)
  • 2) 披薩的製作有 prepare,bake, cut, box
  • 3) 完成披薩店訂購功能。

1,傳統方式

a)代碼

源碼:詳情

public class OrderPizza {
    public OrderPizza() {
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getOrderType();
            if ("chess".equals(orderType)) {
                pizza = new ChessPizza();
                pizza.name = "奶酪";
            }else if ("greek".equals(orderType)) {
                pizza = new GreekPizza();
                pizza.name = "希臘";
            }else if ("china".equals(orderType)) {
                pizza = new ChinaPizza();
                pizza.name = "中國";
            } else {
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }
}

b)uml圖

  

c)優缺點

  • 優點是比較好理解,簡單易操作
  • 缺點是違反了ocp原則,對擴展開發對修改關閉。當需要添加新的pizza種類時需要修改調用方OrderPizza(如果有多個調用方OrderPizza1,OrderPizza2...)。

d)優化

  把創建Pizza對象封裝到一個類(工廠類)中,在訂購時只需要根據不同的orderType就能獲得對應的Pizza類。

2,簡單工廠模式

a)代碼

源碼:詳情

public class SimpleFactory {
    public Pizza getPizza(String type) {
        Pizza pizza = null;
        if ("chess".equals(type)) {
            pizza = new ChessPizza();
            pizza.setName("奶酪");
        }else if ("greek".equals(type)) {
            pizza = new GreekPizza();
            pizza.setName("希臘");
        }else if ("china".equals(type)) {
            pizza = new ChinaPizza();
            pizza.setName("中國");
        }
        return pizza;
    }
}

public class OrderPizza {
    public OrderPizza(SimpleFactory simpleFactory) {
        do {
            String type = getOrderType();
            Pizza pizza = simpleFactory.getPizza(type);
            if (pizza == null) {
                System.out.println("當前pizza的種類沒有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }
}

b)uml圖

  

c)分析

  如果需要重新添加一個pizza的種類,需要在添加一個類繼承Pizza類(可選擇重寫方法),在簡單工廠中添加對應的創建邏輯。不需要在每個OrderPizza類中修改代碼。

3,工廠方法

  需求變動:客戶在點披薩時,可以點不同口味的披薩,比如 北京的奶酪 pizza、北京的胡椒 pizza 或者是倫敦的奶酪 pizza、倫敦的胡椒 pizza。

a)代碼

源碼:詳情

//創建一個抽象的訂購披薩類,根據地區不同都需要繼承這個類,然後再根據口味確定最終的成品
public abstract class OrderPizza {

    abstract Pizza createPizza(String type);

    public OrderPizza() {
        do {
            String type = getOrderType();
            Pizza pizza = createPizza(type);
            if (pizza == null) {
                System.out.println("當前pizza的種類沒有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    private String getOrderType() {
        System.out.println("輸入訂單類型:chess,pepper");
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        System.out.println(next);
        return next;
    }
}
//北京pizza的訂購類
public class BJOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if ("chess".equals(type)) {
            pizza = new BJChessPizza();
            pizza.setName("北京chesses");
        } else if ("pepper".equals(type)) {
            pizza = new BJPepperPizza();
            pizza.setName("北京pepper");
        }
        return pizza;
    }
}
//倫敦pizza的訂購類
public class LDOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        if ("pepper".equals(type)) {
            pizza = new LDPepperPizza();
            pizza.setName("倫敦pepper");
        } else if ("chess".equals(type)) {
            pizza = new LDChessPizza();
            pizza.setName("倫敦chesses");
        }
        return pizza;
    }
}

b)uml圖

   

c)分析

  • 如果這種情況還是使用簡單工廠模式,創建不同的工廠類,比如BJPizzaSimpleFactory、LDPizzaSimpleFactory等,從目前的需求分析是可以的,但是會導致有很多的工廠類,考慮到軟件的可維護性、可擴展性,所以選擇工廠方法模式
  • 工廠方法模式:定義了一個創建對象的抽象方法,由子類決定要實例化的類。工廠方法模式將對象的實例化推遲到子類

4,抽象工廠

a)基本介紹

  抽象工廠模式:定義了一個interface用於創建相關或有依賴關係的對象簇,而無需指明具體的類。抽象工廠模式可以將簡單工廠模式和工廠方法模式進行整合。

  從設計層面看,抽象工廠模式就是對簡單工廠模式的改進(或者稱爲進一步的抽象)。

  將工廠抽象成兩層,AbsFactory(抽象工廠)和具體實現的工廠子類。程序員可以根據創建對象類型使用對應的工廠子類。這樣將單個的簡單工廠類變成了工廠簇,更利於代碼的維護和擴展。

  如果產品的種類很多的話,使用抽象工廠模式的靈活性會很高;如果產品的種類不多的話,使用簡單工廠模式就足夠了

b)代碼

源碼:詳情

  Pizza、BJChessPizza、BJPepperPizza、LDChessPizza、LDPepperPizza與工廠方法基本相同。

//定義抽象工廠類
public interface AbsFactory {
    public Pizza createPizza(String type);
}

//定義倫敦工廠類
public class LDFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String type) {
        if ("chess".equals(type)) {
            return new LDChessPizza();
        } else if ("pepper".equals(type)) {
            return new LDPepperPizza();
        }
        return null;
    }
}

//定義北京工廠類
public class BJFactory implements AbsFactory{
    @Override
    public Pizza createPizza(String type) {
        if ("chess".equals(type)) {
            return new BJChessPizza();
        } else if ("pepper".equals(type)) {
            return new BJPepperPizza();
        }
        return null;
    }
}

//定義訂購類
public class OrderPizza {
    AbsFactory absFactory;

    public OrderPizza(AbsFactory absFactory) {
        setFactory(absFactory);
    }

    private void setFactory(AbsFactory absFactory) {
        this.absFactory = absFactory;
        do {
            String orderType = getOrderType();
            Pizza pizza = absFactory.createPizza(orderType);
            if (pizza == null) {
                System.out.println("當前pizza的種類沒有。");
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }

    private String getOrderType() {
        System.out.println("輸入訂單類型:chess,pepper");
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        System.out.println(next);
        return next;
    }
}

//定義client用戶
public class Test {
    public static void main(String[] args) {
        new OrderPizza(new LDFactory());
    }
}

c)uml圖

  

5,工廠模式在JDK中的應用

    

Calendar中createCalendar源碼:
private static Calendar createCalendar(TimeZone zone,
                                           Locale aLocale){
        CalendarProvider provider =
            LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                                 .getCalendarProvider();
        if (provider != null) {
            try {
                return provider.getInstance(zone, aLocale);
            } catch (IllegalArgumentException iae) {
                // fall back to the default instantiation
            }
        }

        Calendar cal = null;

        if (aLocale.hasExtensions()) {
            String caltype = aLocale.getUnicodeLocaleType("ca");
       //根據不同的caltype類型匹配對應的類
            if (caltype != null) {
                switch (caltype) {
                case "buddhist":
                cal = new BuddhistCalendar(zone, aLocale);
                    break;
                case "japanese":
                    cal = new JapaneseImperialCalendar(zone, aLocale);
                    break;
                case "gregory":
                    cal = new GregorianCalendar(zone, aLocale);
                    break;
                }
            }
        }
        if (cal == null) {
            if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
                cal = new BuddhistCalendar(zone, aLocale);
            } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                       && aLocale.getCountry() == "JP") {
            } else {
                cal = new GregorianCalendar(zone, aLocale);
            }
        }
        return cal;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章