前言
《設計模式自習室》系列,顧名思義,本系列文章帶你溫習常見的設計模式。主要內容有:
- 該模式的介紹,包括: 引子、意圖(大白話解釋) 類圖、時序圖(理論規範)
- 該模式的代碼示例:熟悉該模式的代碼長什麼樣子
- 該模式的優缺點:模式不是萬金油,不可以濫用模式
- 該模式的實際使用案例:瞭解它在哪些重要的源碼中被使用
該系列會逐步更新於我的博客和公眾號(博客見文章底部)
也希望各位觀眾老爺能夠關注我的個人公眾號:後端技術漫談,不會錯過精彩好看的文章。
系列文章回顧
- 【設計模式自習室】開篇:為什麼我們要用設計模式?
- 【設計模式自習室】建造者模式
- 【設計模式自習室】原型模式
- 【設計模式自習室】透徹理解單例模式
創建型——簡單工廠/工廠模式/抽象工廠
引子
工廠模式是一個非常重要的創建型模式,但是工廠模式又分為好多種,並且網上文章很多,很多對工廠模式的定義都不是很明確,甚至還互相沖突,本文希望通過放在一起串講的形式,力求能夠用最簡潔的語言理清工廠模式。
先看一個工廠模式的定義:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基類中定義創建對象的一個接口,讓子類決定實例化哪個類。工廠方法讓一個類的實例化延遲到子類中進行。)
使用了工廠模式,我們可以將對象的創建和使用分離。用來防止用來實例化一個類的數據和代碼在多個類中到處都是。
工廠模式最主要的形式是以下三種:
- 簡單/靜態工廠(Simple Factory)
- 工廠方法(Factory Method)
- 抽象工廠(Abstract Factory)
意圖
1. 簡單/靜態工廠(Simple Factory)
先來看簡單工廠模式,它指的是,在創建一個對象時不向客戶暴露內部細節,並提供一個創建對象的通用接口。
在簡單工廠模式中,可以根據參數的不同返回不同類的實例。
2. 工廠方法(Factory Method)
工廠方法又可以稱為:
- 工廠模式
- 虛擬構造器(Virtual Constructor)模式
- 多態工廠(Polymorphic Factory)模式
工廠模式通過工廠子類來確定究竟應該實例化哪一個具體產品類。不再設計一個工廠類來統一負責所有產品的創建,而是將具體產品的創建過程交給專門的工廠子類去完成。
這一特點無疑使得工廠方法模式具有超越簡單工廠模式的優越性,更加符合“開閉原則”。
3. 抽象工廠(Abstract Factory)
在瞭解抽象工廠之前,我們先要了解下什麼是產品等級結構和產品族
產品族 :在抽象工廠模式中,產品族是指由同一個工廠生產的,位於不同產品等級結構中的一組產品,如海爾電器工廠生產的海爾電視機、海爾電冰箱,海爾電視機位於電視機產品等級結構中,海爾電冰箱位於電冰箱產品等級結構中。
產品等級結構 :產品等級結構即產品的繼承結構,如一個抽象類是電視機,其子類有海爾電視機、海信電視機、TCL電視機,則抽象電視機與具體品牌的電視機之間構成了一個產品等級結構 ,抽象電視機是父類,而具體品牌的電視機是其子類。
工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品對象的創建 。
抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態。
如果看到這裡還是對抽象工廠理解不夠,不要著急,下方的代碼示例會給你加深理解。
類圖
如果看不懂UML類圖,可以先粗略瀏覽下該圖,想深入瞭解的話,可以繼續谷歌,深入學習:
1. 簡單/靜態工廠(Simple Factory)
簡單工廠模式包含如下角色:
- Factory:工廠角色 負責實現創建所有實例的內部邏輯
- Product:抽象產品角色 是所創建的所有對象的父類,負責描述所有實例所共有的公共接口
- ConcreteProduct:具體產品角色 是創建目標,所有創建的對象都充當這個角色的某個具體類的實例。
2. 工廠方法(Factory Method)
(相比簡單工廠,將工廠變為了抽象工廠和具體工廠)
- Factory:抽象工廠,擔任這個角色的是工廠方法模式的核心,任何在模式中創建對象的工廠類必須實現這個接口。在實際的系統中,這個角色也常常使用抽象類實現。
- ConcreteFactory:具體工廠,擔任這個角色的是實現了抽象工廠接口的具體Java類。具體工廠角色含有與業務密切相關的邏輯,並且受到使用者的調用以創建具體產品對象。
- Product:抽象產品,工廠方法模式所創建的對象的超類,也就是所有產品類的共同父類或共同擁有的接口。在實際的系統中,這個角色也常常使用抽象類實現。
- ConcreteProduct:具體產品,這個角色實現了抽象產品(Product)所聲明的接口,工廠方法模式所創建的每一個對象都是某個具體產品的實例。
3. 抽象工廠(Abstract Factory)
抽象工廠模式包含如下角色:
- AbstractFactory:抽象工廠
- ConcreteFactory:具體工廠
- AbstractProduct:抽象產品
- ConcreteProduct:具體產品
你會發現工廠模式和抽象工廠的角色是相同的。
時序圖
時序圖(Sequence Diagram)是顯示對象之間交互的圖,這些對象是按時間順序排列的。時序圖中顯示的是參與交互的對象及其對象之間消息交互的順序。
我們可以大致瀏覽下時序圖,如果感興趣的小夥伴可以去深究一下:
1. 簡單/靜態工廠(Simple Factory)
2. 工廠方法(Factory Method)
3. 抽象工廠(Abstract Factory)
代碼樣例
給出的代碼中,每個類都以角色來區分
1. 簡單/靜態工廠(Simple Factory)
代碼來自:
https://www.jianshu.com/p/d1b6731c1c0e
工廠——LoginManager
<code>public class LoginManager { public static Login factory(String type){ if(type.equals("password")){ return new PasswordLogin(); }else if(type.equals("passcode")){ return new DomainLogin(); }else{ /** * 這裡拋出一個自定義異常會更恰當 */ throw new RuntimeException("沒有找到登錄類型"); } } } /<code>
抽象產品——Login接口
<code>public interface Login { //登錄驗證 public boolean verify(String name , String password); } /<code>
具體產品——PasswordLogin
<code>public class PasswordLogin implements Login { @Override public boolean verify(String name, String password) { // TODO Auto-generated method stub /** * 業務邏輯 */ return true; } } /<code>
客戶端調用
<code>public class Test { public static void main(String[] args) { String loginType = "password"; String name = "name"; String password = "password"; Login login = LoginManager.factory(loginType); boolean bool = login.verify(name, password); if (bool) { /** * 業務邏輯 */ } else { /** * 業務邏輯 */ } } } /<code>
假如不使用上面的簡單工廠模式則驗證登錄Servlet代碼如下,可以看到代碼耦合度很高:
<code>public class Test { public static void main(String[] args) { // TODO Auto-generated method stub String loginType = "password"; String name = "name"; String password = "password"; //處理口令認證 if(loginType.equals("password")){ PasswordLogin passwordLogin = new PasswordLogin(); boolean bool = passwordLogin.verify(name, password); if (bool) { /** * 業務邏輯 */ } else { /** * 業務邏輯 */ } } //處理域認證 else if(loginType.equals("passcode")){ DomainLogin domainLogin = new DomainLogin(); boolean bool = domainLogin.verify(name, password); if (bool) { /** * 業務邏輯 */ } else { /** * 業務邏輯 */ } }else{ /** * 業務邏輯 */ } } } /<code>
2. 工廠方法(Factory Method)
代碼來自:https://www.jianshu.com/p/1cf9859e0f7c
(相比簡單工廠,將工廠變為了抽象工廠和具體工廠)
抽象的產品接口——ILight:具備開關兩種功能的產品
<code>public interface ILight { void TurnOn(); void TurnOff(); } /<code>
具體的產品類——BulbLight
<code> public class TubeLight implements ILight { public void TurnOn() { Console.WriteLine("TubeLight turns on."); } public void TurnOff() { Console.WriteLine("TubeLight turns off."); } } /<code>
抽象的工廠類——ICreator
<code>public interface ICreator { ILight CreateLight(); } /<code>
具體的工廠類——BulbCreator
<code> public class BulbCreator implements ICreator { public ILight CreateLight() { return new BulbLight(); } } /<code>
客戶端調用
<code>static void Main(string[] args) { //先給我來個燈泡 ICreator creator = new BulbCreator(); ILight light = creator.CreateLight(); light.TurnOn(); light.TurnOff(); //再來個燈管看看 creator = new TubeCreator(); light = creator.CreateLight(); light.TurnOn(); light.TurnOff(); } /<code>
本例中每個具體工廠類只負責生產一種類型的產品,當然每個具體工廠類也內部可以維護少數幾種產品實例對象,類似於簡單工廠模式。
3. 抽象工廠(Abstract Factory)
代碼來自:https://www.jianshu.com/p/d6622f3e71ed
抽象產品: 蘋果系列
<code>public interface Apple { void AppleStyle(); } /<code>
抽象產品: 三星系列
<code>public interface Sumsung { void BangziStyle(); } /<code>
具體產品:iphone
<code>public class iphone implements Apple { public void AppleStyle() { Console.WriteLine("Apple's style: iPhone!"); } } /<code>
具體產品:ipad
<code> public class ipad implements Apple { public void AppleStyle() { Console.WriteLine("Apple's style: iPad!"); } } /<code>
具體產品:note2
<code>public class note2 implements Sumsung { public void BangziStyle() { Console.WriteLine("Bangzi's style : Note2!"); } } /<code>
抽象工廠
<code>public interface Factory { Apple createAppleProduct(); Sumsung createSumsungProduct(); } /<code>
手機工廠
<code>public class Factory_Phone implements Factory { public Apple createAppleProduct() { return new iphone(); } public Sumsung createSumsungProduct() { return new note2(); } } /<code>
pad工廠
<code>public class Factory_Pad implements Factory { public Apple createAppleProduct() { return new ipad(); } public Sumsung createSumsungProduct() { return new Tabs(); } } /<code>
客戶端調用
<code>public static void Main(string[] args) { //採購商要一臺iPad和一臺Tab Factory factory = new Factory_Pad(); Apple apple = factory.createAppleProduct(); apple.AppleStyle(); Sumsung sumsung = factory.createSumsungProduct(); sumsung.BangziStyle(); //採購商又要一臺iPhone和一臺Note2 factory = new Factory_Phone(); apple = factory.createAppleProduct(); apple.AppleStyle(); sumsung = factory.createSumsungProduct(); sumsung.BangziStyle(); } /<code>
下面的代碼是剛才已經看過的工廠模式的調用代碼,對比下,發現區別了嗎?工廠方法只需要管是哪個產品族,而抽象工廠還要考慮產品的等級結構,也就是他是蘋果造的,還是三星造的。儘管他們都是手機!
<code>static void Main(string[] args) { //先給我來個燈泡 ICreator creator = new BulbCreator(); ILight light = creator.CreateLight(); light.TurnOn(); light.TurnOff(); //再來個燈管看看 creator = new TubeCreator(); light = creator.CreateLight(); light.TurnOn(); light.TurnOff(); } /<code>
用意圖裡面的話再次總結一下:
工廠方法模式針對的是一個產品等級結構,而抽象工廠模式則需要面對多個產品等級結構,一個工廠等級結構可以負責多個不同產品等級結構中的產品對象的創建 。
優缺點
1. 簡單/靜態工廠(Simple Factory)
優點
- 構造容易,邏輯簡單。
缺點
- 簡單工廠模式中的if else判斷非常多,完全是Hard Code,如果有一個新產品要加進來,就要同時添加一個新產品類,並且必須修改工廠類,再加入一個 else if 分支才可以, 這樣就違背了 “開放-關閉原則“中的對修改關閉的準則了。
- 一個工廠類中集合了所有的類的實例創建邏輯,違反了高內聚的責任分配原則,將全部的創建邏輯都集中到了一個工廠類當中,所有的業務邏輯都在這個工廠類中實現。什麼時候它不能工作了,整個系統都會受到影響。因此一般只在很簡單的情況下應用,比如當工廠類負責創建的對象比較少時。
- 簡單工廠模式由於使用了靜態工廠方法,造成工廠角色無法形成基於繼承的等級結構。
2. 工廠方法(Factory Method)
優點
- 在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
- 工廠方法模式之所以又被稱為多態工廠模式,是因為所有的具體工廠類都具有同一抽象父類。
- 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”,這點比簡單工廠模式更優秀。
缺點
- 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
- 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。
3. 抽象工廠(Abstract Factory)
優點
- 應用抽象工廠模式可以實現高內聚低耦合的設計目的,因此抽象工廠模式得到了廣泛的應用。
- 增加新的具體工廠和產品族很方便,因為一個具體的工廠實現代表的是一個產品族,無須修改已有系統,符合“開閉原則”。
缺點
開閉原則的傾斜性(增加新的工廠和產品族容易,
增加新的產品等級結構麻煩)使用場景舉例
1. 簡單/靜態工廠(Simple Factory)
工廠類負責創建的對象比較少:由於創建的對象較少,不會造成工廠方法中的業務邏輯太過複雜。
Java JDK中使用
- JDK類庫中廣泛使用了簡單工廠模式,如工具類java.text.DateFormat,它用於格式化一個本地日期或者時間。
<code>public final static DateFormat getDateInstance(); public final static DateFormat getDateInstance(int style); public final static DateFormat getDateInstance(int style,Locale locale); /<code>
- Java加密技術
獲取不同加密算法的密鑰生成器:
<code>KeyGenerator keyGen=KeyGenerator.getInstance("DESede"); /<code>
- 創建密碼器:
<code>Cipher cp = Cipher.getInstance("DESede"); /<code>
2. 工廠方法(Factory Method)
Java JDK中使用
- JDBC中的工廠方法:
<code>Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password="); Statement statement=conn.createStatement(); ResultSet rs=statement.executeQuery("select * from UserInfo"); /<code>
- java.util.Calendar
- java.util.ResourceBundle
- java.text.NumberFormat
- java.nio.charset.Charset
- java.net.URLStreamHandlerFactory
- java.util.EnumSet
- javax.xml.bind.JAXBContext
3. 抽象工廠(Abstract Factory)
在以下情況下可以使用抽象工廠模式:
- 一個系統不應當依賴於產品類實例如何被創建、組合和表達的細節,這對於所有類型的工廠模式都是重要的。
- 系統中有多於一個的產品族,而每次只使用其中某一產品族。(與工廠方法模式的區別)
- 屬於同一個產品族的產品將在一起使用,這一約束必須在系統的設計中體現出來。
- 系統提供一個產品類的庫,所有的產品以同樣的接口出現,從而使客戶端不依賴於具體實現。
Java JDK中使用
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
總結
抽象工廠模式中我們可以定義實現不止一個接口,一個工廠也可以生成不止一個產品類,抽象工廠模式較好的實現了“開放-封閉”原則,是三個模式中較為抽象,並具一般性的模式。
但是歸根結底,工廠模式還是一定程度上增加了代碼複雜度,有沒有一種辦法,不需要創建工廠,也能解決代碼以後的擴展性問題呢?
答案是有的,通過控制反轉(ioc),對象在被創建的時候,由一個調控系統內所有對象的外界實體,將其所依賴的對象的引用傳遞給它。也可以說,依賴被注入到對象中。(DI),也就是Spring最核心的思想了。大家可以自行查閱Spring思想的文章。
話說,最近也真的想總結一下Spring源碼相關的知識,正在學習中。
參考
- 《HEAD FIRST 設計模式》
- https://www.jianshu.com/p/d1b6731c1c0e
- https://www.jianshu.com/p/1cf9859e0f7c
- https://www.jianshu.com/p/d6622f3e71ed
- https://design-patterns.readthedocs.io/zh_CN/latest/creational_patterns/abstract_factory.html
關注我
我是一名後端開發工程師。
主要關注後端開發,數據安全,爬蟲,物聯網,邊緣計算等方向,歡迎交流。
各大平臺都可以找到我
- 微信公眾號:後端技術漫談
- Github:@qqxx6661
- CSDN:@Rude3Knife
- 知乎:@Zhendong
- 簡書:@蠻三刀把刀
- 掘金:@蠻三刀把刀
原創博客主要內容
- Java面試知識點複習全手冊
- 設計模式/數據結構 自習室
- Leetcode/劍指offer 算法題解析
- SpringBoot/SpringCloud菜鳥入門實戰系列
- 爬蟲相關技術文章
- 後端開發相關技術文章
- 逸聞趣事/好書分享/個人興趣
個人公眾號:後端技術漫談
公眾號:後端技術漫談.jpg
如果文章對你有幫助,不妨收藏,投幣,轉發,在看起來~