從BWM生產學習工廠模式
工廠模式應用非常之廣,在JDK
底層原始碼以及各大主流框架中隨處可見,一般以Factory
結尾命名的類,比如Mybatis
中的SqlSessionFactory
,Spring
中的BeanFactory
等,都是工廠模式的典型代表。
一、簡單工廠模式
1.1 概念
簡單工廠模式又稱為靜態工廠模式,屬於設計模式中的建立型模式。簡單工廠模式通過對外提供一個靜態方法來統一為類建立例項,目的是實現類與類之間解耦:客戶端不需要知道這個物件是如何被穿創建出來的,只需要呼叫簡單工廠模式的方法來統一建立就可以了,從而明確了各個類的職責。
1.2 示例
簡單工廠模式,以生產汽車輪胎為例。
1.2.1 實體類
- 輪胎通用屬性
public class Tire {
/**
* 通用屬性
*/
private String common;
}
- 賓士車輪胎
包含通用屬性外還有自己的特有屬性
public class TireForBenz extends Tire{ Tire tire; /** * 特有屬性 */ private String benz; public TireForBenz() { this.benz = "得到 Benz 輪胎"; } @Override public String toString() { return "["+this.benz +"]"; } }
- 寶馬車輪胎
包含通用屬性外還有自己的特有屬性
public class TireForBwm extends Tire{
Tire tire;
/**
* 特有屬性
*/
private String bwm;
public TireForBwm() {
this.bwm = "得到 Bwm 輪胎";
}
@Override
public String toString() {
return "["+this.bwm +"]";
}
}
1.2.2 生產工藝
- 生產輪胎的抽象方法,各個產線有自己的方式生產
public interface TireFactory {
Tire produceTire();
}
- 賓士汽車輪胎產線
重寫生產輪胎的方法返回賓士型輪胎。
public class BenzTireFactory implements TireFactory {
/**
* 生產賓士輪胎
*/
@Override
public Tire produceTire() {
System.out.println("賓士輪胎生產中。。。");
return new TireForBenz();
}
}
- 寶馬汽車輪胎產線
重寫生產輪胎的方法返回寶馬型輪胎。
public class BwmTireFactory implements TireFactory {
/**
* 生產寶馬輪胎
*/
@Override
public TireForBwm produceTire() {
System.out.println("寶馬輪胎生產中。。。");
return new TireForBwm();
}
}
1.2.3 輪胎工廠類
通過傳入的品牌名稱呼叫相應產線生產相應品牌的輪胎
public class SimpleFactoryMode {
public static TireFactory produceCar(String name) {
if ("BenzTireFactory".equals(name)) {
return new BenzTireFactory();
}
if ("BwmTireFactory".equals(name)) {
return new BwmTireFactory();
}
return null;
}
}
1.2.4 測試
客戶端通過工廠類獲取例項物件。
- 測試方法
@Test
public void simpleFactoryModeTest() {
// 造賓士輪胎
TireFactory benz = SimpleFactoryMode.produceCar("BenzTireFactory");
if (null != benz) {
benz.produceTire();
}else {
System.out.println("工廠暫時無法生產賓士輪胎");
}
// 造寶馬輪胎
TireFactory bwm = SimpleFactoryMode.produceCar("BwmTireFactory");
if (null != bwm) {
bwm.produceTire();
}else {
System.out.println("工廠暫時無法生產寶馬輪胎");
}
// 造本田汽輪胎(工廠無該方法)
TireFactory honda = SimpleFactoryMode.produceCar("Honda");
if (null != honda) {
honda.produceTire();
}else {
System.out.println("工廠暫時無法生產本田輪胎");
}
}
- 結果
賓士輪胎生產中。。。
寶馬輪胎生產中。。。
工廠暫時無法生產本田輪胎
該方式確實能完成不同品牌的輪胎生產,但是,有個問題:方法引數是字串,可控性有待提升。
1.3 簡單工廠模式優化
不要通過傳入的字串來判斷需要建立物件,而是客戶端想要建立什麼物件,只需要傳入具體的實現類就可以了,然後通過
Java
的反射來建立物件。
public static TireFactory produceCar(Class<? extends TireFactory> clazz) {
try {
// 通過Java的反射來建立物件
return clazz.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
每次建立物件都是通過反射來建立的,所以在效能上是有一定的損耗。
- 測試
public void simpleFactoryModeUpgradeTest() {
// 造賓士輪胎
TireFactory benzTire = SimpleFactoryMode.produceCar(BenzTireFactory.class);
TireForBenz benz = (TireForBenz) benzTire.produceTire();
System.out.println(benz.toString());
// 造寶馬輪胎
TireFactory bwmTire = SimpleFactoryMode.produceCar(BwmTireFactory.class);
TireForBwm bwm = (TireForBwm) bwmTire.produceTire();
System.out.println(bwm.toString());
}
- 結果
賓士輪胎生產中。。。
[得到 Benz 輪胎]
寶馬輪胎生產中。。。
[得到 Bwm 輪胎]
1.4 小結
簡單工廠模式確實在一定程度上實現程式碼的解耦,而這種解耦的特點在於,這種模式將物件的建立和使用分離。這種模式的本質在於通過一個傳入的引數,做if...else
判斷,來達到返回不同型別物件的目的。缺點也很明顯,不符合開閉原則(比如新增一個保時捷輪胎的生產,除了需要增加實體和生產方法,還需要修改工廠類SimpleFactoryMode.java
)。因此,如果需要增加新的型別,就不得不去修改原來的程式碼,違反開閉原則。
- 簡單工廠模式優點:
- 簡單優化了軟體體系結構,明確了各自功能模組的職責和權利;
- 通過工廠類,外界不需要直接建立具體產品物件,只需要負責消費,不需要關心內部如何建立物件。
- 簡單工廠模式缺點:
- 改進前的簡單工廠模式全部建立邏輯都集中在一個工廠類中,能建立的類只能是考慮到的,如果需要新增新的類,就必須改變工廠類了;
- 改進前的簡單工廠模式隨著具體產品的不斷增多,可能會出現共產類根據不同條件建立不同例項的需求,這種對條件的判斷和對具體產品型別的判斷交錯在一起,很難避免功能模組的蔓延,對系統的維護和擴充套件不利;
- 改進後的簡單工廠模式主要是使用反射效率會低一些。
二、工廠方法模式
- 簡單工廠模式之所以違反開閉原則,關鍵在於什麼?
那就是它把所有物件的建立都集中在同一個工廠類裡面了,因此,當新增一個新物件時,必然會需要修改這個共享工廠類,違反開閉原則自然不可避免。
- 解決方案
既然問題關鍵在於,所有物件的建立都跟這個唯一的工廠類耦合了,那我每個物件各自都配置一個單獨的工廠類,這個工廠類只建立各自型別的物件,那這樣不就解決耦合的問題了嗎?
2.1 概念
工廠方法模式是指定義一個建立物件的介面,但讓實現這個介面的類來決定例項化哪個類。工廠方法讓類的例項化推遲到子類中進行。在工廠方法模式中使用者只需要關心所需產品對應的工廠,無須關心建立細節,而且加入新的產品符合開閉原則。
2.2 示例
工廠方法模式,以生產發動機為例。
2.2.1 實體
- 發動機的通用屬性
public class Engine {
/**
* 型號
*/
private String common;
}
- 賓士發動機
包含通用屬性外還有自己的特有屬性
public class EngineForBenz extends Engine{
Engine engine;
/**
* 特有屬性
*/
private String benz;
public EngineForBenz() {
this.benz = "得到 Benz 發動機";
}
@Override
public String toString() {
return "["+this.benz +"]";
}
}
- 寶馬發動機
包含通用屬性外還有自己的特有屬性
public class EngineForBwm extends Engine{
Engine engine;
/**
* 特有屬性
*/
private String bwm;
public EngineForBwm() {
this.bwm = "得到 Bwm 發動機";
}
@Override
public String toString() {
return "["+this.bwm +"]";
}
}
2.2.2 生產工藝(發動機的工廠類)
- 抽象工廠類,定義生產發動機的方法,各個產線自己的去實現
public interface EngineFactory<T> {
Engine produceEngine();
}
- 建立賓士子工廠,實現其的工藝
public class BenzEngineFactory implements EngineFactory<EngineForBenz> {
/**
* 生產賓士發動機
*/
@Override
public Engine produceEngine() {
System.out.println("賓士發動機生產中。。。");
return new EngineForBenz();
}
}
- 建立寶馬子工廠,實現其的工藝
public class BwmEngineFactory implements EngineFactory<EngineForBwm> {
/**
* 生產寶馬發動機
*/
@Override
public Engine produceEngine() {
System.out.println("寶馬發動機生產中。。。");
return new EngineForBwm();
}
}
2.2.3 測試
@Test
public void factoryModeTest() {
// 造賓士發動機
EngineFactory car = new BenzEngineFactory();
EngineForBenz benz = (EngineForBenz) car.produceEngine();
System.out.println(benz.toString());
// 造寶馬發動機
EngineFactory carFactory = new BwmEngineFactory();
EngineForBwm bwm = (EngineForBwm) carFactory.produceEngine();
System.out.println(bwm.toString());
}
- 結果
賓士發動機生產中。。。
[得到 Benz 發動機]
寶馬發動機生產中。。。
[得到 Bwm 發動機]
2.3 小結
工廠方法模式輕鬆解決了簡單工廠模式的問題,符合開閉原則。在上面例子中,當需要新增一個保時捷汽車,此時只需要提供一個對應的EngineForBSJ.java
實現produceEngine()
方法即可,對於原先程式碼再不需要做任何修改。
但是每個型別的物件都會有一個與之對應的工廠類。如果物件的型別非常多,意味著會需要建立很多的工廠實現類,造成類數量膨脹,對後續維護帶來一些麻煩。
- 缺點
- 客戶端(應用層)不依賴於產品類例項如何被建立、實現等細節;
- 一個類通過其子類來指定建立哪個物件。
- 缺點
- 類的個數容易過多,增加複雜度;
- 增加了系統的抽象性和理解難度。
三、抽象工廠模式
抽象工廠模式出現,就是為了解決上述工廠方法模式存在的問題,可以看成是工廠方法模式的升級。
3.1 背景
- 類數量膨脹的情景
工廠方法模式建立的物件其實歸根到底都是同一類物件。以汽車生產為例,無論是輪胎還是發動機,都是汽車生產的一部分,都是屬於汽車生產的過程。如下圖:
由上圖我們可以發現,雖然分為賓士車和寶馬車,但是從工廠方法角度,他們都屬於汽車這一類別,這就導致了需要單獨為每一個零件指定各自的工廠類,從而導致了類數量膨脹的問題。
- 解決方案
既然這樣,我們可以把每類汽車指定一個工廠,然後再讓不同產線去生產他需要的產品,如下圖
這樣當每一類物品元件數量特別多,可以把它稱為產品族。抽象工廠模式就是為了建立一系列以產品族為單位的物件,這樣在需要建立大量系列物件時可以大大提高開發效率,降低維護成本。
3.2 示例
因為賓士輪胎/寶馬輪胎/賓士發動機/寶馬發動機的實體在前面已經建立過,這裡就直接用了。
3.2.1 汽車工廠類(頂層抽象工廠類)
該類已包含輪胎/發動機生產,具體實體鍵一/二中相關實體。
public interface CarFactory {
/**
* 準備生產
*/
void init();
/**
* 生產輪胎
* @return
*/
Tire produceTire();
/**
* 生產發動機
* @return
*/
Engine produceEngine();
}
3.2.2 賓士汽車產品族(賓士汽車工廠類)
public class BenzCarFactory implements CarFactory{
@Override
public void init() {
System.out.println("----------------------- 賓士汽車準備生產 -----------------------");
}
@Override
public Tire produceTire() {
System.out.println("正在生產賓士輪胎");
return new TireForBenz();
}
@Override
public Engine produceEngine() {
System.out.println("正在生產賓士發動機");
return new EngineForBenz();
}
}
3.2.2 寶馬汽車產品族(寶馬汽車工廠類)
public class BwmCarFactory implements CarFactory{
@Override
public void init() {
System.out.println("----------------------- 寶馬汽車準備生產 -----------------------");
}
@Override
public Tire produceTire() {
System.out.println("正在生產寶馬輪胎");
return new TireForBwm();
}
@Override
public Engine produceEngine() {
System.out.println("正在生產寶馬發動機");
return new EngineForBwm();
}
}
3.2.3 測試
@Test
public void abstractFactoryModeTest() {
// 生產賓士整車的零部件
CarFactory benz = new BenzCarFactory();
benz.init();
TireForBenz benzTire = (TireForBenz) benz.produceTire();
System.out.println(benzTire.toString());
EngineForBenz benzEngine = (EngineForBenz) benz.produceEngine();
System.out.println(benzEngine.toString());
// 生成寶馬整車的零部件d
CarFactory bwm = new BwmCarFactory();
bwm.init();
TireForBwm bwmTire = (TireForBwm) bwm.produceTire();
System.out.println(bwmTire.toString());
EngineForBwm bwmEngine = (EngineForBwm) bwm.produceEngine();
System.out.println(bwmEngine.toString());
}
- 結果
----------------------- 賓士汽車準備生產 -----------------------
正在生產賓士輪胎
[得到 Benz 輪胎]
正在生產賓士發動機
[得到 Benz 發動機]
----------------------- 寶馬汽車準備生產 -----------------------
正在生產寶馬輪胎
[得到 Bwm 輪胎]
正在生產寶馬發動機
[得到 Bwm 發動機]
3.3 思考
既然說抽象工廠模式是工廠方法模式的升級,那到底升級了啥?
其實是由原來的單一產品的生產升級成為了系列產品的生產。設想一下,假設上面汽車的例子中,每一品牌汽車中就只生產一種部件,比如就只生產發動機,不生產輪胎等其他元件了,如下圖
發現了什麼沒有?抽象工廠模式居然轉變為我們之前講過的工廠方法模式了!換句話說,當你的產品族中只生產一種產品的時候,你的抽象工廠模式其實已經退化為工廠方法模式了。反過來說,當生產多種產品時,工廠方法模式就進化為抽象工廠模式。
3.4 小結
抽象工廠模式在建立大量系列物件時可以大大提高開發效率,就是為生產產品族而生的,而對於生產單一產品卻無能為力。
如果需要新增一個新的產品族,那就簡單了,比如新增一個保時捷汽車,那就只需要新增一個保時捷汽車的工廠實現類就好了,並不會對原有的程式碼造成任何影響。
但是,如果假設在汽車中,我需要再加一個元件,比如倒車影像,怎麼操作?你需要在CarFactory
介面中新增返回倒車影像物件的介面。這一加不得了了......所有品牌汽車實現類全部需要修改並追加該方法的實現,違反了開閉原則。
- 抽象工廠模式優點:
建立大量系列物件時可以大大提高開發效率,降低維護成本。
- 抽象工廠模式缺點:
- 規定了所有可能被建立的產品集合,產品族中擴充套件新的產品困難,需要修改抽象工廠的介面;
- 增加了系統的抽象性和理解難度。
四、總結
4.1 如何選擇
工廠模式的三種形式都介紹完了,那我們實際開發中該如何去選擇呢?
- 從設計原則來說,簡單工廠模式不符合開閉原則。但是很神奇,在實際場景中,簡單工廠模式確實用的最多的。
- 工廠方法模式是專門用於解決單個物件建立工作,本身模式沒問題,也符合開閉原則。但是存在工廠類數量膨脹的問題。如果需要建立的工廠類不是很多,是一種不錯的選擇。
- 抽象工廠模式天生就是為生產產品族而生的。所以如果你需要建立的物件非常之多,但是物件之間存在明顯產品族特徵,那麼這個時候用抽象工廠模式非常合適。
4.2 示例原始碼
Github 示例程式碼
4.3 技術交流
- 風塵部落格:https://www.dustyblog.cn
- 風塵部落格-掘金
- 風塵部落格-部落格園
- Github
- 公眾號
參考文章