前言
《設(shè)計模式自習(xí)室》系列,顧名思義涝开,本系列文章帶你溫習(xí)常見的設(shè)計模式循帐。主要內(nèi)容有:
- 該模式的介紹,包括:
- 引子舀武、意圖(大白話解釋)
- 類圖拄养、時序圖(理論規(guī)范)
- 該模式的代碼示例:熟悉該模式的代碼長什么樣子
- 該模式的優(yōu)缺點:模式不是萬金油,不可以濫用模式
- 該模式的實際使用案例:了解它在哪些重要的源碼中被使用
該系列會逐步更新于我的博客和公眾號(博客見文章底部)
也希望各位觀眾老爺能夠關(guān)注我的個人公眾號:后端技術(shù)漫談银舱,不會錯過精彩好看的文章瘪匿。
系列文章回顧
- 【設(shè)計模式自習(xí)室】開篇:為什么我們要用設(shè)計模式?
- 【設(shè)計模式自習(xí)室】建造者模式
- 【設(shè)計模式自習(xí)室】原型模式
- 【設(shè)計模式自習(xí)室】透徹理解單例模式
創(chuàng)建型——簡單工廠/工廠模式/抽象工廠
引子
工廠模式是一個非常重要的創(chuàng)建型模式寻馏,但是工廠模式又分為好多種棋弥,并且網(wǎng)上文章很多,很多對工廠模式的定義都不是很明確诚欠,甚至還互相沖突顽染,本文希望通過放在一起串講的形式,力求能夠用最簡潔的語言理清工廠模式轰绵。
先看一個工廠模式的定義:
“Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.”(在基類中定義創(chuàng)建對象的一個接口粉寞,讓子類決定實例化哪個類。工廠方法讓一個類的實例化延遲到子類中進(jìn)行左腔。)
使用了工廠模式唧垦,我們可以將對象的創(chuàng)建和使用分離。用來防止用來實例化一個類的數(shù)據(jù)和代碼在多個類中到處都是液样。
工廠模式最主要的形式是以下三種:
- 簡單/靜態(tài)工廠(Simple Factory)
- 工廠方法(Factory Method)
- 抽象工廠(Abstract Factory)
意圖
1. 簡單/靜態(tài)工廠(Simple Factory)
先來看簡單工廠模式振亮,它指的是,在創(chuàng)建一個對象時不向客戶暴露內(nèi)部細(xì)節(jié)鞭莽,并提供一個創(chuàng)建對象的通用接口坊秸。
在簡單工廠模式中,可以根據(jù)參數(shù)的不同返回不同類的實例澎怒。
2. 工廠方法(Factory Method)
工廠方法又可以稱為:
- 工廠模式
- 虛擬構(gòu)造器(Virtual Constructor)模式
- 多態(tài)工廠(Polymorphic Factory)模式
工廠模式通過工廠子類來確定究竟應(yīng)該實例化哪一個具體產(chǎn)品類褒搔。不再設(shè)計一個工廠類來統(tǒng)一負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體產(chǎn)品的創(chuàng)建過程交給專門的工廠子類去完成丹拯。
這一特點無疑使得工廠方法模式具有超越簡單工廠模式的優(yōu)越性站超,更加符合“開閉原則”。
3. 抽象工廠(Abstract Factory)
在了解抽象工廠之前乖酬,我們先要了解下什么是產(chǎn)品等級結(jié)構(gòu)和產(chǎn)品族
產(chǎn)品族 :在抽象工廠模式中死相,產(chǎn)品族是指由同一個工廠生產(chǎn)的,位于不同產(chǎn)品等級結(jié)構(gòu)中的一組產(chǎn)品咬像,如海爾電器工廠生產(chǎn)的海爾電視機算撮、海爾電冰箱生宛,海爾電視機位于電視機產(chǎn)品等級結(jié)構(gòu)中,海爾電冰箱位于電冰箱產(chǎn)品等級結(jié)構(gòu)中肮柜。
產(chǎn)品等級結(jié)構(gòu) :產(chǎn)品等級結(jié)構(gòu)即產(chǎn)品的繼承結(jié)構(gòu)陷舅,如一個抽象類是電視機,其子類有海爾電視機审洞、海信電視機莱睁、TCL電視機,則抽象電視機與具體品牌的電視機之間構(gòu)成了一個產(chǎn)品等級結(jié)構(gòu)芒澜,抽象電視機是父類仰剿,而具體品牌的電視機是其子類。
工廠方法模式針對的是一個產(chǎn)品等級結(jié)構(gòu)痴晦,而抽象工廠模式則需要面對多個產(chǎn)品等級結(jié)構(gòu)南吮,一個工廠等級結(jié)構(gòu)可以負(fù)責(zé)多個不同產(chǎn)品等級結(jié)構(gòu)中的產(chǎn)品對象的創(chuàng)建 。
抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)誊酌。
如果看到這里還是對抽象工廠理解不夠部凑,不要著急,下方的代碼示例會給你加深理解碧浊。
類圖
如果看不懂UML類圖涂邀,可以先粗略瀏覽下該圖,想深入了解的話辉词,可以繼續(xù)谷歌必孤,深入學(xué)習(xí):
1. 簡單/靜態(tài)工廠(Simple Factory)
簡單工廠模式包含如下角色:
- Factory:工廠角色 負(fù)責(zé)實現(xiàn)創(chuàng)建所有實例的內(nèi)部邏輯
- Product:抽象產(chǎn)品角色 是所創(chuàng)建的所有對象的父類猾骡,負(fù)責(zé)描述所有實例所共有的公共接口
- ConcreteProduct:具體產(chǎn)品角色 是創(chuàng)建目標(biāo)瑞躺,所有創(chuàng)建的對象都充當(dāng)這個角色的某個具體類的實例。
2. 工廠方法(Factory Method)
(相比簡單工廠兴想,將工廠變?yōu)榱顺橄蠊S和具體工廠)
- Factory:抽象工廠幢哨,擔(dān)任這個角色的是工廠方法模式的核心,任何在模式中創(chuàng)建對象的工廠類必須實現(xiàn)這個接口嫂便。在實際的系統(tǒng)中捞镰,這個角色也常常使用抽象類實現(xiàn)。
- ConcreteFactory:具體工廠毙替,擔(dān)任這個角色的是實現(xiàn)了抽象工廠接口的具體Java類岸售。具體工廠角色含有與業(yè)務(wù)密切相關(guān)的邏輯,并且受到使用者的調(diào)用以創(chuàng)建具體產(chǎn)品對象厂画。
- Product:抽象產(chǎn)品凸丸,工廠方法模式所創(chuàng)建的對象的超類,也就是所有產(chǎn)品類的共同父類或共同擁有的接口袱院。在實際的系統(tǒng)中屎慢,這個角色也常常使用抽象類實現(xiàn)瞭稼。
- ConcreteProduct:具體產(chǎn)品,這個角色實現(xiàn)了抽象產(chǎn)品(Product)所聲明的接口腻惠,工廠方法模式所創(chuàng)建的每一個對象都是某個具體產(chǎn)品的實例环肘。
3. 抽象工廠(Abstract Factory)
抽象工廠模式包含如下角色:
- AbstractFactory:抽象工廠
- ConcreteFactory:具體工廠
- AbstractProduct:抽象產(chǎn)品
- ConcreteProduct:具體產(chǎn)品
你會發(fā)現(xiàn)工廠模式和抽象工廠的角色是相同的。
時序圖
時序圖(Sequence Diagram)是顯示對象之間交互的圖集灌,這些對象是按時間順序排列的悔雹。時序圖中顯示的是參與交互的對象及其對象之間消息交互的順序。
我們可以大致瀏覽下時序圖欣喧,如果感興趣的小伙伴可以去深究一下:
1. 簡單/靜態(tài)工廠(Simple Factory)
2. 工廠方法(Factory Method)
3. 抽象工廠(Abstract Factory)
代碼樣例
給出的代碼中荠商,每個類都以角色來區(qū)分
1. 簡單/靜態(tài)工廠(Simple Factory)
代碼來自:
http://www.reibang.com/p/d1b6731c1c0e
工廠——LoginManager
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{
/**
* 這里拋出一個自定義異常會更恰當(dāng)
*/
throw new RuntimeException("沒有找到登錄類型");
}
}
}
抽象產(chǎn)品——Login接口
public interface Login {
//登錄驗證
public boolean verify(String name , String password);
}
具體產(chǎn)品——PasswordLogin
public class PasswordLogin implements Login {
@Override
public boolean verify(String name, String password) {
// TODO Auto-generated method stub
/**
* 業(yè)務(wù)邏輯
*/
return true;
}
}
客戶端調(diào)用
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) {
/**
* 業(yè)務(wù)邏輯
*/
} else {
/**
* 業(yè)務(wù)邏輯
*/
}
}
}
假如不使用上面的簡單工廠模式則驗證登錄Servlet代碼如下,可以看到代碼耦合度很高:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
String loginType = "password";
String name = "name";
String password = "password";
//處理口令認(rèn)證
if(loginType.equals("password")){
PasswordLogin passwordLogin = new PasswordLogin();
boolean bool = passwordLogin.verify(name, password);
if (bool) {
/**
* 業(yè)務(wù)邏輯
*/
} else {
/**
* 業(yè)務(wù)邏輯
*/
}
}
//處理域認(rèn)證
else if(loginType.equals("passcode")){
DomainLogin domainLogin = new DomainLogin();
boolean bool = domainLogin.verify(name, password);
if (bool) {
/**
* 業(yè)務(wù)邏輯
*/
} else {
/**
* 業(yè)務(wù)邏輯
*/
}
}else{
/**
* 業(yè)務(wù)邏輯
*/
}
}
}
2. 工廠方法(Factory Method)
代碼來自:http://www.reibang.com/p/1cf9859e0f7c
(相比簡單工廠续誉,將工廠變?yōu)榱顺橄蠊S和具體工廠)
抽象的產(chǎn)品接口——ILight:具備開關(guān)兩種功能的產(chǎn)品
public interface ILight
{
void TurnOn();
void TurnOff();
}
具體的產(chǎn)品類——BulbLight
public class TubeLight implements ILight
{
public void TurnOn()
{
Console.WriteLine("TubeLight turns on.");
}
public void TurnOff()
{
Console.WriteLine("TubeLight turns off.");
}
}
抽象的工廠類——ICreator
public interface ICreator
{
ILight CreateLight();
}
具體的工廠類——BulbCreator
public class BulbCreator implements ICreator
{
public ILight CreateLight()
{
return new BulbLight();
}
}
客戶端調(diào)用
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();
}
本例中每個具體工廠類只負(fù)責(zé)生產(chǎn)一種類型的產(chǎn)品莱没,當(dāng)然每個具體工廠類也內(nèi)部可以維護少數(shù)幾種產(chǎn)品實例對象,類似于簡單工廠模式酷鸦。
3. 抽象工廠(Abstract Factory)
代碼來自:http://www.reibang.com/p/d6622f3e71ed
抽象產(chǎn)品: 蘋果系列
public interface Apple
{
void AppleStyle();
}
抽象產(chǎn)品: 三星系列
public interface Sumsung
{
void BangziStyle();
}
具體產(chǎn)品:iphone
public class iphone implements Apple
{
public void AppleStyle()
{
Console.WriteLine("Apple's style: iPhone!");
}
}
具體產(chǎn)品:ipad
public class ipad implements Apple
{
public void AppleStyle()
{
Console.WriteLine("Apple's style: iPad!");
}
}
具體產(chǎn)品:note2
public class note2 implements Sumsung
{
public void BangziStyle()
{
Console.WriteLine("Bangzi's style : Note2!");
}
}
抽象工廠
public interface Factory
{
Apple createAppleProduct();
Sumsung createSumsungProduct();
}
手機工廠
public class Factory_Phone implements Factory
{
public Apple createAppleProduct()
{
return new iphone();
}
public Sumsung createSumsungProduct()
{
return new note2();
}
}
pad工廠
public class Factory_Pad implements Factory
{
public Apple createAppleProduct()
{
return new ipad();
}
public Sumsung createSumsungProduct()
{
return new Tabs();
}
}
客戶端調(diào)用
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();
}
下面的代碼是剛才已經(jīng)看過的工廠模式的調(diào)用代碼饰躲,對比下,發(fā)現(xiàn)區(qū)別了嗎臼隔?工廠方法只需要管是哪個產(chǎn)品族嘹裂,而抽象工廠還要考慮產(chǎn)品的等級結(jié)構(gòu),也就是他是蘋果造的摔握,還是三星造的寄狼。盡管他們都是手機!
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();
}
用意圖里面的話再次總結(jié)一下:
工廠方法模式針對的是一個產(chǎn)品等級結(jié)構(gòu)氨淌,而抽象工廠模式則需要面對多個產(chǎn)品等級結(jié)構(gòu)泊愧,一個工廠等級結(jié)構(gòu)可以負(fù)責(zé)多個不同產(chǎn)品等級結(jié)構(gòu)中的產(chǎn)品對象的創(chuàng)建 。
優(yōu)缺點
1. 簡單/靜態(tài)工廠(Simple Factory)
優(yōu)點
- 構(gòu)造容易盛正,邏輯簡單删咱。
缺點
- 簡單工廠模式中的if else判斷非常多,完全是Hard Code豪筝,如果有一個新產(chǎn)品要加進(jìn)來痰滋,就要同時添加一個新產(chǎn)品類,并且必須修改工廠類续崖,再加入一個 else if 分支才可以敲街, 這樣就違背了 “開放-關(guān)閉原則“中的對修改關(guān)閉的準(zhǔn)則了。
- 一個工廠類中集合了所有的類的實例創(chuàng)建邏輯严望,違反了高內(nèi)聚的責(zé)任分配原則多艇,將全部的創(chuàng)建邏輯都集中到了一個工廠類當(dāng)中,所有的業(yè)務(wù)邏輯都在這個工廠類中實現(xiàn)著蟹。什么時候它不能工作了墩蔓,整個系統(tǒng)都會受到影響梢莽。因此一般只在很簡單的情況下應(yīng)用,比如當(dāng)工廠類負(fù)責(zé)創(chuàng)建的對象比較少時奸披。
- 簡單工廠模式由于使用了靜態(tài)工廠方法昏名,造成工廠角色無法形成基于繼承的等級結(jié)構(gòu)。
2. 工廠方法(Factory Method)
優(yōu)點
- 在工廠方法模式中阵面,工廠方法用來創(chuàng)建客戶所需要的產(chǎn)品轻局,同時還向客戶隱藏了哪種具體產(chǎn)品類將被實例化這一細(xì)節(jié),用戶只需要關(guān)心所需產(chǎn)品對應(yīng)的工廠样刷,無須關(guān)心創(chuàng)建細(xì)節(jié)仑扑,甚至無須知道具體產(chǎn)品類的類名。
- 工廠方法模式之所以又被稱為多態(tài)工廠模式置鼻,是因為所有的具體工廠類都具有同一抽象父類镇饮。
- 使用工廠方法模式的另一個優(yōu)點是在系統(tǒng)中加入新產(chǎn)品時,無須修改抽象工廠和抽象產(chǎn)品提供的接口箕母,無須修改客戶端储藐,也無須修改其他的具體工廠和具體產(chǎn)品,而只要添加一個具體工廠和具體產(chǎn)品就可以了嘶是。這樣钙勃,系統(tǒng)的可擴展性也就變得非常好,完全符合“開閉原則”聂喇,這點比簡單工廠模式更優(yōu)秀辖源。
缺點
- 在添加新產(chǎn)品時,需要編寫新的具體產(chǎn)品類希太,而且還要提供與之對應(yīng)的具體工廠類克饶,系統(tǒng)中類的個數(shù)將成對增加,在一定程度上增加了系統(tǒng)的復(fù)雜度跛十,有更多的類需要編譯和運行彤路,會給系統(tǒng)帶來一些額外的開銷秕硝。
- 由于考慮到系統(tǒng)的可擴展性芥映,需要引入抽象層,在客戶端代碼中均使用抽象層進(jìn)行定義远豺,增加了系統(tǒng)的抽象性和理解難度奈偏,且在實現(xiàn)時可能需要用到DOM、反射等技術(shù)躯护,增加了系統(tǒng)的實現(xiàn)難度惊来。
3. 抽象工廠(Abstract Factory)
優(yōu)點
- 應(yīng)用抽象工廠模式可以實現(xiàn)高內(nèi)聚低耦合的設(shè)計目的,因此抽象工廠模式得到了廣泛的應(yīng)用棺滞。
- 增加新的具體工廠和產(chǎn)品族很方便裁蚁,因為一個具體的工廠實現(xiàn)代表的是一個產(chǎn)品族矢渊,無須修改已有系統(tǒng),符合“開閉原則”枉证。
缺點
開閉原則的傾斜性(增加新的工廠和產(chǎn)品族容易矮男,增加新的產(chǎn)品等級結(jié)構(gòu)麻煩)
使用場景舉例
1. 簡單/靜態(tài)工廠(Simple Factory)
工廠類負(fù)責(zé)創(chuàng)建的對象比較少:由于創(chuàng)建的對象較少,不會造成工廠方法中的業(yè)務(wù)邏輯太過復(fù)雜室谚。
Java JDK中使用
- JDK類庫中廣泛使用了簡單工廠模式毡鉴,如工具類java.text.DateFormat,它用于格式化一個本地日期或者時間秒赤。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale
locale);
- Java加密技術(shù)
獲取不同加密算法的密鑰生成器:
KeyGenerator keyGen=KeyGenerator.getInstance("DESede");
- 創(chuàng)建密碼器:
Cipher cp = Cipher.getInstance("DESede");
2. 工廠方法(Factory Method)
Java JDK中使用
- JDBC中的工廠方法:
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");
- 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)
在以下情況下可以使用抽象工廠模式:
- 一個系統(tǒng)不應(yīng)當(dāng)依賴于產(chǎn)品類實例如何被創(chuàng)建猪瞬、組合和表達(dá)的細(xì)節(jié),這對于所有類型的工廠模式都是重要的入篮。
- 系統(tǒng)中有多于一個的產(chǎn)品族陈瘦,而每次只使用其中某一產(chǎn)品族。(與工廠方法模式的區(qū)別)
- 屬于同一個產(chǎn)品族的產(chǎn)品將在一起使用潮售,這一約束必須在系統(tǒng)的設(shè)計中體現(xiàn)出來甘晤。
- 系統(tǒng)提供一個產(chǎn)品類的庫,所有的產(chǎn)品以同樣的接口出現(xiàn)饲做,從而使客戶端不依賴于具體實現(xiàn)线婚。
Java JDK中使用
- javax.xml.parsers.DocumentBuilderFactory
- javax.xml.transform.TransformerFactory
- javax.xml.xpath.XPathFactory
總結(jié)
抽象工廠模式中我們可以定義實現(xiàn)不止一個接口,一個工廠也可以生成不止一個產(chǎn)品類盆均,抽象工廠模式較好的實現(xiàn)了“開放-封閉”原則塞弊,是三個模式中較為抽象,并具一般性的模式泪姨。
但是歸根結(jié)底游沿,工廠模式還是一定程度上增加了代碼復(fù)雜度,有沒有一種辦法肮砾,不需要創(chuàng)建工廠诀黍,也能解決代碼以后的擴展性問題呢?
答案是有的仗处,通過控制反轉(zhuǎn)(ioc)眯勾,對象在被創(chuàng)建的時候,由一個調(diào)控系統(tǒng)內(nèi)所有對象的外界實體婆誓,將其所依賴的對象的引用傳遞給它吃环。也可以說洋幻,依賴被注入到對象中郁轻。(DI),也就是Spring最核心的思想了。大家可以自行查閱Spring思想的文章好唯。
話說竭沫,最近也真的想總結(jié)一下Spring源碼相關(guān)的知識,正在學(xué)習(xí)中骑篙。