創建對象可能會導致大量的重覆代碼,可能會需要複合對象訪問不到的信息,也可能提供不了足夠級別的抽象,還可能並不是複合對象概念的一部分。工廠設計模式通過定義一個單獨的創建對象的方法來解決這些問題。 ...
簡介
工廠設計模式(Factory Design Pattern)是一種創建型的設計模式,它提供了一種創建對象的最佳方式,是一種代替 new
操作符的一種模式。
在工廠模式中,創建對象不會對客戶端暴露創建邏輯,而是通過使用一個共同的介面來指向新創建的對象。
工廠模式還可以細分為三種的類型:簡單工廠模式、工廠方法模式和抽象工廠模式。
簡單工廠模式
概念
簡單工廠模式(Simple Factory Pattern)的定義是,由一個工廠對象決定創建出哪一種產品類的實例,被創建的產品類實例具有共同的父類或實現同樣的介面。
因為在簡單工廠模式中用於創建實例的方法通常是靜態方法,因此簡單工廠模式又被稱為靜態工廠模式(Static Factory Pattern)。
實現方式
下麵是使用果汁生產來展示簡單工廠模式:
果汁 FruitJuice 介面:描述生產果汁的必要方法
public interface FruitJuice {
void make();
}
蘋果汁 AppleJuice 類:生產蘋果汁的類
public class AppleJuice implements FruitJuice {
public AppleJuice() {
this.make();
}
@Override
public void make() {
System.out.println("This is AppleJuice make!");
}
}
橙汁 OrangeJuice 類:生產橙汁的類
public class OrangeJuice implements FruitJuice {
public OrangeJuice() {
this.make();
}
@Override
public void make() {
System.out.println("This is OrangeJuice make!");
}
}
果汁工廠 FruitJuiceFactory 類:負責生產各種果汁的類
public class FruitJuiceFactory {
public FruitJuiceFactory() {}
public static FruitJuice createFruitJuice(String juiceType) {
FruitJuice fruitJuice = null;
if (juiceType.equals("AppleJuice")) {
fruitJuice = new AppleJuice();
} else if (juiceType.equals("OrangeJuice")) {
fruitJuice = new OrangeJuice();
}
return fruitJuice;
}
}
通過 if-else 邏輯是簡單工廠的第一種實現方法,還可以通過靜態代碼塊和 Map 結構實現簡單工廠模式,具體的代碼示例如下:
import java.util.Map;
import java.util.HashMap;
public class FruitJuiceFactory {
// 存儲產品類的映射關係
private static final Map<String, FruitJuice> cachedFruitJuice = new HashMap<>();
static {
cachedFruitJuice.put("AppleJuice", new AppleJuice());
cachedFruitJuice.put("OrangeJuice", new OrangeJuice());
}
public FruitJuiceFactory() {}
public static FruitJuice createFruitJuice(String juiceType) {
if (juiceType == null || juiceType.isEmpty()) {
return null;
}
// 直接從 Map 結構中取到對應的產品類實例
return cachedFruitJuice.get(juiceType.toLowerCase());
}
}
優點
簡單工廠模式的主要優點如下:
- 簡單工廠模式實現了對象創建和使用的分離
- 客戶端無需知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可
- 可以在不改變客戶端代碼的情況下更換或增加新的具體產品類
缺點
簡單工廠模式的主要缺點如下:
- 工廠類集中了相似產品類的創建邏輯,職責過重
- 簡單工廠模式違反開閉原則,新增產品類時需要改動到工廠類
- 通常使用靜態方法作為創建實例的方法,無法實現繼承關係
適用場景
簡單工廠模式的適用場景如下:
- 對於一批產品類,並且不會新增產品類,可以選擇簡單工廠模式
源碼
在 JDK 中,java.util.Calendar
使用了簡單工廠模式。如下是其的一些實現邏輯:
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar> {
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) {
// fall back to the default instantiation
}
}
Calendar cal = null;
if (aLocale.hasExtensions()) {
String caltype = aLocale.getUnicodeLocaleType("ca");
if (caltype != null) {
cal = switch (caltype) {
case "buddhist" -> new BuddhistCalendar(zone, aLocale);
case "japanese" -> new JapaneseImperialCalendar(zone, aLocale);
case "gregory" -> new GregorianCalendar(zone, aLocale);
default -> null;
};
}
}
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;
}
}
工廠方法模式
概念
簡單工廠模式違背了開閉原則,沒有能夠做到“對擴展開放、對修改關閉”,新增產品類時需要改動到工廠類。
而工廠方法模式(Factory Method Pattern)是對簡單工廠模式的進一步抽象,其好處是可以使系統在不修改原來代碼的情況下引入新的產品類,即滿足開閉原則。
和簡單工廠模式中工廠負責生產所有的產品相比,工廠方法模式抽象出簡單工廠的介面,然後將生產具體產品的任務分發給具體的產品工廠,即簡單工廠的工廠。
由於工廠方法模式利用了面向對象的多態特性,因此又被稱為多態工廠模式(Polymorphic Factory Pattern)。
實現方式
在上述簡單工廠模式的基礎上,將果汁生產改造成工廠方法模式:
抽象工廠 AbstractFactory 介面:描述生產果汁工廠的必要方法
public interface FruitJuiceFactory {
FruitJuice createFruitJuice();
}
蘋果汁工廠 AppleJuiceFactory 類:詳細的生產蘋果汁的類
public class AppleJuiceFactory implements FruitJuiceFactory {
public AppleJuiceFactory() {}
@Override
public FruitJuice createFruitJuice() {
return new AppleJuice();
}
}
橙汁工廠 OrangeJuiceFactory 類:詳細的生產橙汁的類
public class OrangeJuiceFactory implements FruitJuiceFactory {
public OrangeJuiceFactory() {}
@Override
public FruitJuice createFruitJuice() {
return new OrangeJuice();
}
}
優點
工廠方法模式除了具有簡單工廠模式的優點之外,還有以下優點:
- 在系統中增加新的產品類時,無需修改原有的工廠代碼,只需添加一個具體產品類和具體工廠類
缺點
工廠方法模式的主要缺點如下:
- 添加新的產品類時,需要同時提供具體產品類和對應的工廠類,系統中類的個數將成對增加
- 工廠方法模式引入了抽象層,增加了系統的抽象性和理解難度
適用場景
工廠方法模式的適用場景如下:
- 客戶端知道其需要的介面實現類,不知道所需要的具體對象類,可以通過對應的工廠創建實例
源碼
在 JDK 中,java.util.Collection
介面中定義的 iterator()
方法就是一個工廠方法。
所有實現了 Collection
介面的類,都需要顯式地實現此方法並返回一個 Iterator
實例,不同的實現類可以擁有自己的實現邏輯。
抽象工廠模式
概念
在工廠方法模式中,具體工廠負責生產具體的產品,每個具體工廠對應一種具體產品,工廠方法具有唯一性。
抽象工廠模式(Abstract Factory Pattern)定義了一個介面用於創建相關或有依賴關係的對象族,而無需指明具體的類,其可以整合簡單工廠模式和抽象工廠模式。
學習抽象工廠模式需要理解兩個概念:產品等級結構指的是產品的繼承結構,如抽象果汁和具體果汁之間構成一個產品等級結構;產品族指的是由同一個工廠生產的,位於不同等級結構中的一組產品,如同一個飲料工廠生產的酒和果汁構成一個產品族。
從上述概念上理解,工廠方法模式針對的是一個產品等級結構,而抽象工廠模式在工廠方法模式的基礎上,覆蓋了多個工廠的產品族。
實現方式
在上述簡單工廠模式的基礎上,下麵使用果汁生產和酒生產來展示抽象工廠模式:
酒 Alcohol 介面:描述生產酒的必要方法
public interface Alcohol {
void make();
}
葡萄酒 Wine 類:詳細的生產葡萄酒的類
public class Wine implements Alcohol {
public Wine() {
this.make();
}
@Override
public void make() {
System.out.println("This is Wine make!");
}
}
啤酒 Beer 類:詳細的生產啤酒的類
public class Beer implements Alcohol {
public Beer() {
this.make();
}
@Override
public void make() {
System.out.println("This is Beer make!");
}
}
抽象工廠 AbstractFactory 介面:描述生產果汁和酒的必要方法
public interface AbstractFactory {
FruitJuice creatFruitJuice();
Alcohol createAlcohol();
}
具體工廠 ConcreteFactory 類:可生產果汁和酒的工廠的類
public class ConcreteFactory implements AbstractFactory {
public ConcreteFactory() {}
@Override
public FruitJuice creatFruitJuice(String juiceType) {
FruitJuice fruitJuice = null;
if (juiceType.equals("AppleJuice")) {
fruitJuice = new AppleJuice();
} else if (juiceType.equals("OrangeJuice")) {
fruitJuice = new OrangeJuice();
}
return fruitJuice;
}
@Override
public Alcohol createAlcohol(String alcoholType) {
Alcohol alcohol = null;
if (alcoholType.equals("Wine")) {
alcohol = new Wine();
} else if (alcoholType.equals("Beer")) {
alcohol = new Beer();
}
return alcohol;
}
}
優點
抽象工廠模式的主要優點如下:
- 當一個產品族中的多個對象被設計成一起工作時,它能夠保證客戶端始終只使用同一個產品族中的對象
- 增加新的產品族很方便,無需修改已有代碼,符合開閉原則
缺點
抽象工廠模式的主要缺點如下:
- 增加新的產品等級結構麻煩,需要對原有系統進行較大的修改,甚至需要修改抽象層代碼
適用場景
抽象工廠模式的適用場景如下:
- 當需要創建的對象是一系列相互關聯或相互依賴的產品族時,便可以使用抽象工廠模式
- 產品等級結構穩定,設計完成之後,不會向系統中增加新的產品等級結構或者刪除已有的產品等級結構
源碼
在 JDK 中,java.util.Collection
介面可以看作一個抽象工廠。
Collection
介面定義了轉換成 Iterator
實例的工廠方法,也定義了轉換成 T[]
實例的工廠方法,可以認定其做了兩個產品族的定義。
總結
使用標準
對於要不要使用工廠模式,其最本質的參考標準有以下四個:
- 封裝變化:創建邏輯有可能變化,封裝成工廠類之後,創建邏輯的變更對調用者透明
- 代碼復用:創建代碼抽離到獨立的工廠類之後可以復用
- 隔離複雜性:封裝複雜的創建邏輯,調用者無需瞭解如何創建對象
- 控制複雜度:將創建代碼抽離出來,讓原本的函數或類職責更單一,代碼更簡潔
應用場景
對於產品種類相對較少、且可預見性地不會修改的情況,可以使用簡單工廠模式。使用簡單工廠模式的客戶端只需傳入工廠類的參數,不需要關心如何創建對象的邏輯,可以很方便地創建所需產品。
由於簡單工廠模式會將所有創建邏輯都放在一個工廠類中,會導致這個工廠類會變得很複雜,當產品的種類是可預見地會增加時,還需要對工廠類做更改,這種時候可以採用工廠方法模式以達到不需要修改原來代碼的情況下引進新的產品。
抽象工廠模式最早的應用是用於創建屬於不同操作系統的視窗構件。當需要創建的對象是一系列相互關聯或相互依賴的產品族時,或者是只會新增新的產品族而不是新種類的產品時,可以使用抽象工廠模式。