設計模式 | 簡單工廠模式及典型應用
設計模式(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
抽象類,該類的子類有 BuddhistCalendar
、 JapaneseImperialCalendar
、 GregorianCalendar
、 RollingCalendar
等
getInstance
方法,根據引數獲取一個 Calendar
子類物件,該方法實際將引數傳給 createCalendar
方法, createCalendar
在根據引數通過 provider
或 switch
或者 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
的子類擔任
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()); } } // ...省略... } 複製程式碼

工廠角色為 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); } 複製程式碼
檢視其子類依賴關係

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

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

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