設計模式 | 簡單工廠模式及典型應用

小旋鋒發表於2019-03-01

前言

設計模式(Design Pattern)是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結,使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解並且保證程式碼可靠性。

本文主要介紹簡單工廠模式及典型應用,內容如下:

  • 簡單工廠模式的介紹
  • 簡單工廠模式的典型應用及原始碼分析
    • Calendar 類獲取日曆類物件
    • JDBC 獲取資料庫連線
    • LoggerFactory 獲取 Logger 物件

簡單工廠模式

工廠模式是最常用的一類建立型設計模式,包括 抽象工廠模式,工廠方法模式和簡單工廠模式 這三種,簡單工廠模式是其中最簡單的一種

簡單工廠模式(Simple Factory Pattern):定義一個工廠類,它可以根據引數的不同返回不同類的例項,被建立的例項通常都具有共同的父類。

因為在簡單工廠模式中用於建立例項的方法是靜態(static)方法,因此簡單工廠模式又被稱為靜態工廠方法(Static Factory Method)模式,它屬於類建立型模式,但不屬於GOF23種設計模式

角色

Factory(工廠角色):工廠角色即工廠類,它是簡單工廠模式的核心,負責實現建立所有產品例項的內部邏輯;工廠類可以被外界直接呼叫,建立所需的產品物件;在工廠類中提供了靜態的工廠方法factoryMethod(),它的返回型別為抽象產品型別Product

Product(抽象產品角色):它是工廠類所建立的所有物件的父類,封裝了各種產品物件的公有方法,它的引入將提高系統的靈活性,使得在工廠類中只需定義一個通用的工廠方法,因為所有建立的具體產品物件都是其子類物件。

ConcreteProduct(具體產品角色):它是簡單工廠模式的建立目標,所有被建立的物件都充當這個角色的某個具體類的例項。每一個具體產品角色都繼承了抽象產品角色,需要實現在抽象產品中宣告的抽象方法

在簡單工廠模式中,客戶端通過工廠類來建立一個產品類的例項,而無須直接使用new關鍵字來建立物件,它是工廠模式家族中最簡單的一員

示例

抽象產品類 Video,定義了抽象方法 produce()

public abstract class Video {
    public abstract void produce();
}
複製程式碼

具體產品類 JavaVideo 和 PythonVideo,都繼承了抽象產品類 Video

public class JavaVideo extends Video {
    @Override
    public void produce() {
        System.out.println("錄製Java課程視訊");
    }
}

public class PythonVideo extends Video {
    @Override
    public void produce() {
        System.out.println("錄製Python課程視訊");
    }
}
複製程式碼

工廠類實現的兩種方法:使用if-else判斷和使用反射來建立物件

public class VideoFactory {
    /**
     * 使用if else 判斷型別,type 為 Java 則返回 JavaVideo, type為Python則返回 PythonVideo
     */
    public Video getVideo(String type) {
        if ("java".equalsIgnoreCase(type)) {
            return new JavaVideo();
        } else if ("python".equalsIgnoreCase(type)) {
            return new PythonVideo();
        }
        return null;
    }

    /**
     * 使用反射來建立物件
     */
    public Video getVideo(Class c) {
        Video video = null;
        try {
            video = (Video) Class.forName(c.getName()).newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        return video;
    }
}
複製程式碼

使用一個客戶端來呼叫工廠類

public class Test {
    public static void main(String[] args) {
        VideoFactory videoFactory = new VideoFactory();
        Video video1 = videoFactory.getVideo("python");
        if (video1 == null) {
            return;
        }
        video1.produce();

        Video video2 = videoFactory.getVideo(JavaVideo.class);
        if (video2 == null) {
            return;
        }
        video2.produce();
    }
}
複製程式碼

輸出

錄製Python課程視訊
錄製Java課程視訊
複製程式碼
示例.簡單工廠模式類圖

Test 類通過傳遞引數給 VideoFactory.getVideo() 來獲取物件,建立物件的邏輯交給了工廠類 VideoFactory 來完成

簡單工廠模式總結

簡單工廠模式的主要優點如下:

  • 工廠類包含必要的判斷邏輯,可以決定在什麼時候建立哪一個產品類的例項,客戶端可以免除直接建立產品物件的職責,而僅僅“消費”產品,簡單工廠模式實現了物件建立和使用的分離。
  • 客戶端無須知道所建立的具體產品類的類名,只需要知道具體產品類所對應的引數即可,對於一些複雜的類名,通過簡單工廠模式可以在一定程度減少使用者的記憶量。
  • 通過引入配置檔案,可以在不修改任何客戶端程式碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。

簡單工廠模式的主要缺點如下:

  • 由於工廠類集中了所有產品的建立邏輯,職責過重,一旦不能正常工作,整個系統都要受到影響。
  • 使用簡單工廠模式勢必會增加系統中類的個數(引入了新的工廠類),增加了系統的複雜度和理解難度。
  • 系統擴充套件困難,一旦新增新產品就不得不修改工廠邏輯,在產品型別較多時,有可能造成工廠邏輯過於複雜,不利於系統的擴充套件和維護,且違背開閉原則。
  • 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。

適用場景

  • 工廠類負責建立的物件比較少,由於建立的物件較少,不會造成工廠方法中的業務邏輯太過複雜。
  • 客戶端只知道傳入工廠類的引數,對於如何建立物件並不關心。

簡單工廠模式的典型應用及原始碼分析

Calendar 類獲取日曆類物件

Calendar 抽象類,該類的子類有 BuddhistCalendarJapaneseImperialCalendarGregorianCalendarRollingCalendar

getInstance方法,根據引數獲取一個Calendar子類物件,該方法實際將引數傳給 createCalendar 方法,createCalendar 在根據引數通過 providerswitch 或者 if-else 建立相應的子類物件

以下為 Java8 中的 Calendar 類程式碼,Java7 中的實現為 if-else 方式

public static Calendar getInstance(TimeZone zone, Locale aLocale) {
    return createCalendar(zone, aLocale);
}

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) {
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        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") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}
複製程式碼
Calendar的繼承關係

可以看到抽象產品角色工廠角色都由 Calendar 擔任,具體產品角色Calendar 的子類擔任

JDBC 獲取資料庫連線

一般JDBC獲取MySQL連線的寫法如下:

//載入MySql驅動
Class.forName("com.mysql.jdbc.Driver");
DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test", "root", "123456");
複製程式碼

首先通過反射載入驅動類 com.mysql.jdbc.Driver 類,然後再通過 DriverManager 獲取連線

看看 com.mysql.jdbc.Driver 的程式碼,該類主要的內容是靜態程式碼塊,其會隨著類的載入一塊執行

public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    public Driver() throws SQLException {
    }
    static {
        try {
            DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
            throw new RuntimeException("Can`t register driver!");
        }
    }
}
複製程式碼

靜態程式碼塊:new 一個 Driver 類並註冊到 DriverManager 驅動管理類中

public static synchronized void registerDriver(java.sql.Driver driver, DriverAction da) throws SQLException {
    /* Register the driver if it has not already been added to our list */
    if(driver != null) {
        registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
    } else {
        throw new NullPointerException();
    }
    println("registerDriver: " + driver);
}
複製程式碼

其中的 registeredDrivers 是一個 CopyOnWriteArrayList 物件

private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
複製程式碼

CopyOnWriteArrayList是Java併發包中提供的一個併發容器,它是個執行緒安全且讀操作無鎖的ArrayList,寫操作則通過建立底層陣列的新副本來實現,是一種讀寫分離的併發策略,我們也可以稱這種容器為”寫時複製器”,Java併發包中類似的容器還有CopyOnWriteSet
一篇CopyOnWriteArrayList的文章:www.cnblogs.com/chengxiao/p…

再通過 DriverManager.getConnection 獲取連線物件的主要程式碼如下:通過for迴圈從已註冊的驅動中(registeredDrivers)獲取驅動,嘗試連線,成功則返回連線

private static Connection getConnection(String url, java.util.Properties info, Class<?> caller) throws SQLException {
    // ...省略...
    println("DriverManager.getConnection("" + url + "")");
    for(DriverInfo aDriver : registeredDrivers) {
        // If the caller does not have permission to load the driver then skip it.
        if(isDriverAllowed(aDriver.driver, callerCL)) {
            try {
                println("    trying " + aDriver.driver.getClass().getName());
                Connection con = aDriver.driver.connect(url, info);
                if (con != null) {
                    // Success!
                    println("getConnection returning " + aDriver.driver.getClass().getName());
                    return (con);
                }
            } catch (SQLException ex) {
                if (reason == null) {
                    reason = ex;
                }
            }
        } else {
            println("    skipping: " + aDriver.getClass().getName());
        }
    }
    // ...省略...
}
複製程式碼
Connection 介面及子類實現關係

工廠角色為 DriverManager 類,抽象產品角色為 Connection,具體產品角色則很多

Logback 中的 LoggerFactory 獲取 Logger 物件

檢視 LoggerFactory 類的 getLogger 方法,可看到呼叫了 iLoggerFactory.getLogger(),其中 iLoggerFactory 是一個介面

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

public static Logger getLogger(Class clazz) {
    return getLogger(clazz.getName());
}
複製程式碼

iLoggerFactory 介面只有一個 getLogger 方法

public interface ILoggerFactory {
    Logger getLogger(String var1);
}
複製程式碼

檢視其子類依賴關係

iLoggerFactory介面子類的依賴關係

再看一個子類 LoggerContext 對 ILoggerFactory 的實現

image

可看到這是通過 if-else 方式的簡單工廠模式

Logger 介面及子類實現關係

工廠角色為 iLoggerFactory 介面的子類如 LoggerContext,抽象產品角色為 Logger,具體產品角色為 Logger 的子類,主要是 NOPLoggerLogger

小結

下一篇介紹工廠方法及典型應用

原文請點選: t.cn/Rs6rkYI

參考:
劉偉:設計模式Java版
慕課網java設計模式精講 Debug 方式+記憶體分析


更多內容請訪問我的個人部落格:laijianfeng.org/

開啟微信掃一掃,關注【小旋鋒】微信公眾號,及時接收博文推送

小旋鋒的微信公眾號

相關文章