一文搞懂三種工廠模式
上一篇文章詳細學習了單例模式的多種寫法,今天來學習一下如下三種模式:簡單工廠、工廠方法、抽象工廠模式,其實簡單工廠模式不屬於 GOF 23 種設計模式,不過它實現簡單,在有些場景下它也比較適用,所以就首先來看一下它。
簡單工廠模式
通常我們使用 new 關鍵字就可以建立物件,為什麼還要使用工廠模式呢?我們以下面這個例子來看一下。
如果有一個手機店,出售 IPhone、Samsung、Huawei 品牌的手機。
public class Phone { public void pay() {} public void box() {} } class IPhone extends Phone { } class Samsung extends Phone { } class Huawei extends Phone { } 複製程式碼
顧客在購買手機的程式碼可以這樣寫:
public class PhoneStore { public Phone buyPhone(String type) { Phone phone = null; if ("Iphone".equals(type)) { phone = new IPhone(); } else if ("Samsung".equals(type)) { phone = new Samsung(); } else if ("Huawei ".equals(type)) { phone = new Huawei(); } phone.pay(); phone.box(); return phone; } } 複製程式碼
如果店鋪想要增加競爭力,又添加了幾種手機品牌,就需要去修改 buyPhone 方法,在其中繼續新增 if-else 語句。
也就是說,如果程式碼有變化或擴充套件,就必須重新修改該方法,這就違反了對擴充套件開放、對修改關閉的原則。而且這樣修改對於系統來說,將難以維護和更新。
其實,我們可以將建立物件的程式碼移到另一個物件,封裝成一個工廠類,在新增或改變手機的品牌時,只需要修改該工廠類即可:
public class SimplePhoneFactory { public static Phone createPhone(String type) { Phone phone = null; if ("Iphone".equals(type)) { phone = new IPhone(); } else if ("Samsung".equals(type)) { phone = new Samsung(); } else if ("Huawei ".equals(type)) { phone = new Huawei(); } return phone; } } 複製程式碼
而 PhoneStore 的程式碼就可以修改為:
public class PhoneStore { public Phone buyPhone(String type) { Phone phone = SimplePhoneFactory.createPhone(type); phone.pay(); phone.box(); return phone; } } 複製程式碼
上述模式就是簡單工廠模式,也可以利用靜態方法來定義工廠,這稱為靜態工廠。
我們來看一下它的 UML 圖:

下面來總結一下,簡單工廠模式有哪些優缺點。
優點:
- 如果一個呼叫者想建立一個物件,只要知道其名稱就可以了。
- 如果想增加一個產品,只要實現一個新的擴充套件自工廠類的子類就可以了。
- 它遮蔽了產品的具體實現,呼叫者只需要關心產品的介面。
但它也有一些缺點:
- 每次增加產品時,都需要增加一個產品類,使得系統中的類太多,增加了系統的複雜度。
簡單工廠模式具體實踐
Calendar#createCalendar
該方法部分原始碼如下:
private static Calendar createCalendar(TimeZone zone, Locale aLocale) ··· 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); } } 複製程式碼
這裡會根據傳入的語言和國家來決定生成什麼 Calendar。
工廠方法模式
如果手機店規模比較大,希望開設 IPhone、Samsung、Huawei 專賣分店(假設分店自己製作手機),這應該如何擴充套件呢?
由於購買手機的過程類似,都需要付款、打包;而分店則需要生產其相應品牌的手機。我們可以將 PhoneStore 修改為抽象類 ,將 SimplePhoneFactory 的 createPhone 方法改為抽象方法,放置到 AbstractPhoneStore 中。
public abstract class PhoneStore { public Phone buyPhone() { Phone phone = createPhone(); phone.pay(); phone.box(); return phone; } protected abstract Phone createPhone(); } 複製程式碼
IPhone、Samsung、Huawei 三種產品分別如下:
public class Phone { public void pay() { } public void box() { } } class IPhone extends Phone { } class Samsung extends Phone { } class Huawei extends Phone { } 複製程式碼
三種產品對應的 IPhone、Samsung、Huawei 三家分店,它們的具體實現如下:
public class IPhoneStore extends PhoneStore { @Override protected Phone createPhone() { return new IPhone(); } } public class SamsungStore extends PhoneStore { @Override protected Phone createPhone() { return new Samsung(); } } public class HuaweiStore extends PhoneStore { @Override protected Phone createPhone() { return new Huawei(); } } 複製程式碼
如果我們要 IPhone 手機,程式碼可以如下:
public class Client { public static void main(String[] args) { PhoneStore phoneStore = new IPhoneStore(); Phone phone = phoneStore.buyPhone(); // phone 為 IPhone } } 複製程式碼
上述這種模式就是工廠方法模式,它會定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類。例如這裡建立了一個 PhoneStore 抽象類,但實際上由 IPhoneStore 來決定例項化哪個 Phone 的實現類。
我們可以看到工廠方法模式包括了四個角色:
- Product:抽象產品,對應上面的 Phone;
- ConcreteProduct:具體產品,對應 IPhone、Samsung、Huawei 等;
- Factory:抽象工廠,對應 PhoneStore;
- ConcreteFactory:具體工廠,對應 IPhoneStore、SamsungStore、HuaweiStore。
它的 UML 圖如下:

下面總結一下工廠方法模式的優點:
- 使用者只需要關心所需產品對應的工廠,而無需關心建立產品的細節;
- 在系統中加入新產品時,無需修改抽象工廠和抽象產品的介面,也無需修改其他的具體工廠和具體產品,只需要新增一個具體的工廠和具體產品就可以了。
缺點:
- 每新增一個新產品就要新增對應的工廠和產品,造成系統中類的個數太多,增加了系統的複雜度。
- 考慮系統的擴充套件性,需要引入抽象層,增加了系統的抽象性,系統實現的難度也加大。
簡單工廠模式與工廠方法模式之間的區別如下:
- 在簡單工廠中,是將物件的建立封裝在另一個類中;
- 而在工廠方法中,它建立了一個框架,由子類來決定建立哪個物件。
工廠方法模式具體實踐
Java 集合的 iterator 方法就是一個工廠方法。部分集合的 UML 圖如下:

抽象工廠
該例項中抽象工廠就是 Iterable 介面:
public interface Iterable<T> { Iterator<T> iterator(); } 複製程式碼
具體工廠
具體工廠在 Java 集合中非常多,這裡舉兩個例子,例如在 ArrayList 中的實現:
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable public Iterator<E> iterator() { return new Itr(); } } 複製程式碼
在 HashMap 中的實現中,entrySet 方法返回一個 EntrySet 物件:
final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } } 複製程式碼
抽象產品
抽象產品就是 Iterator 介面
public interface Iterator<E> { boolean hasNext(); E next(); } 複製程式碼
具體產品
這裡的具體產品,以上面說的 ArrayList 中的 Itr 和 EntrySet 中的 EntryIterator 為例。
Itr 物件如下:
private class Itr implements Iterator<E> { public boolean hasNext() { ··· } public E next() { ··· } } 複製程式碼
EntryIterator 物件如下:
abstract class HashIterator { public final boolean hasNext() { ··· } final Node<K,V> nextNode() { ··· } } final class EntryIterator extends HashIterator implements Iterator<Map.Entry<K,V>> { public final Map.Entry<K,V> next() { return nextNode(); } } 複製程式碼
抽象工廠模式
如果想要改變商業模式,三家專賣店內不僅可以賣 IPhone、Sansung、Huawei 三種品牌的手機,也可以賣相應品牌的電腦(假設分店自己生產手機、電腦),這應該如何設計呢?(這個例子不太符合實際情況,不過能說明抽象工廠模式的含義,湊合看吧)
店鋪可以如下設計:
public abstract class PhoneStore { public Phone buyPhone() { Phone phone = createPhone(); phone.pay(); phone.box(); return phone; } public Computer buyComputer() { Computer computer = createComputer(); computer.pay(); computer.pack(); return computer; } protected abstract Phone createPhone(); protected abstract Computer createComputer(); } 複製程式碼
三種品牌的手機類如下:
public class Phone { public void pay() { } public void box() { } } class IPhone extends Phone { } class SamsungPhone extends Phone { } class HuaweiPhone extends Phone { } 複製程式碼
三種品牌的電腦類如下:
public class Computer { public void pay() {} public void pack() { } } class MacComputer extends Computer { } class SamsungComputer extends Computer { } class HuaweiComputer extends Computer { } 複製程式碼
對於三家相應品牌的專賣店,它們的具體實現如下:
public class IPhoneStore extends PhoneStore { @Override protected Phone createPhone() { return new IPhone(); } @Override protected Computer createComputer() { return new MacComputer(); } } public class SamsungStore extends PhoneStore { @Override protected Phone createPhone() { return new SamsungPhone(); } @Override protected Computer createComputer() { return new SamsungComputer(); } } public class HuaweiStore extends PhoneStore { @Override protected Phone createPhone() { return new HuaweiPhone(); } @Override protected Computer createComputer() { return new HuaweiComputer(); } } 複製程式碼
如果我們要在 IPhone 專賣店購買手機和電腦,程式碼可以如下:
public class Test { public static void main(String[] args) { PhoneStore phoneStore = new IPhoneStore(); Phone phone = phoneStore.buyPhone(); Computer computer = phoneStore.buyComputer(); // phone 為 IPhone // computer 為 Mac } } 複製程式碼
上述的模式就是抽象工工廠模式,它提供了一個介面,用於建立一個產品的家族,而不需要指定具體類。每個具體工廠會建立某個產品家族。
在上述例子,IPhoneStore、SamsungStore、HuaweiStore 就是一個個具體的工廠,它們可以生產對應品牌的手機和電腦。其中 IPhoneStore 這個工廠就是建立 IPhone、MacComputer 這個產品家族。
它的 UML 圖如下:

下面總結一下抽象工廠模式的優缺點。
優點:
- 同樣地,將使用者程式碼和實際的具體產品解耦,使其無需關心產品的建立細節;
- 使用某個工廠,可以建立一系列相關的產品。如果想要增加一條個產品線,例如上面想要增加一個新的品牌店和其相應的產品,只需要擴充套件 PhoneStore 工廠,並建立相應的 Phone、Computer 類即可,非常簡單。
缺點:
- 限制了所能建立的產品集合,例如上面的 Phone 和 Computer,如果想要增加新的產品,增加 camera,就會比較困難,需要修改抽象工廠的介面,會增加很大的工作量;
- 另外,工廠類和產品類較多,增加了系統的抽象性和複雜度。
抽象工廠模式與工廠方法模式很類似,它們之間的區別如下:
- 在工廠方法模式中,每個具體工廠負責建立一個具體產品。所以,在增加一個具體產品時,也要增加其相應的工廠,需要建立一個繼承自抽象工廠的類,並覆蓋它的工廠方法。也就是所說的工廠方法使用繼承建立物件。
- 而在抽象工廠模式中,每個具體工廠負責建立一個系列的具體產品。所以,只有在新增加一個型別的具體產品時,才需要增加相應的工廠。它可以用來建立一系列具體產品,將這些相關的產品組合起來,也就是所說的使用組合建立物件。
它們之間也有一些關聯,就是抽象工廠的方法以工廠方法的方法來實現。在抽象工廠的介面中,每個方法都負責建立一個具體產品,而具體工廠來提供具體的實現。
例如,PhoneStore 中的 createPhone、createComputer 方法由子類實現,這兩個方法單獨來看都是在建立一個物件,其實也就是一個工廠方法。
抽象工廠模式的實踐
JDBC 中的 Connection 就是一個抽象工廠模式,在不同的連線池中有不同的實現,例如 druid 和 dbcp:

由於本人對於 druid 和 dbcp 的實現也不太熟悉,這裡就不多解釋了,有興趣的小夥伴可以自己研究一下。
參考資料
- 《Head First 設計模式》
- CyC2018/CS-Notes: 設計模式
- 越努力越幸運-致自己: java設計模式精講 Debug 方式+記憶體分析 第4章 簡單工廠模式