建立型模式之單例模式與工廠模式(一)

MXC肖某某發表於2021-09-01

一、基本介紹

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

  具體的建立型模式可分為:

  • 單例模式(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;
}

 

相關文章