設計模式——工廠方法模式(汽車工廠)
本文首發於 cdream 的個人部落格,點選獲得更好的閱讀體驗!
歡迎轉載,轉載請註明出處。
本文主要講述工廠方法模式,並與簡單方法模式進行對比。以汽車製造廠為例進行講解。
一、概念
定義:工廠方法模式(Factory method pattern)是指工廠父類負責定義建立產品物件的公共介面,而工廠子類則負責生成具體的產品物件,這樣做的目的是將產品類的例項化操作推遲到工廠子類中完成,即通過工廠子類來確定究竟應該例項化哪一個具體產品類。
工廠方法模式主要解決兩個問題:
1.定義一個建立物件的單獨操作(工廠方法)
2.將建立物件推遲到子類完成
二、簡單工廠模式
在說工廠方法模式前先簡述一下簡單工廠模式,在 Head First 設計模式 中提到:簡單工廠模式與其說是一種設計模式倒不如說是一種程式設計習慣。
過去在製造物件時,我們經常使用new 物件這種操作,不過一提到new,那就是面向具體程式設計,面向實現程式設計,不方便進行修改和維護。
舉個簡單的例子:在沒有工廠方法之前,專賣店出售汽車時都要在方法裡面new 物件
public class CarStore{ public Car saleCar(String type){ Car car; if (type.equals("A4")){ car = new AduiA4(); } else if (type.equals("A6")){ car = new AudiA6(); } else if (type.equals("A8")){ car = new AudiA8(); } // 售前檢查 car.check(); // 汽車出庫 car.outStore(); return car; } }
看看,這樣的設計有什麼缺點?
- 如果哪天新增加了奧迪Q5,奧迪Q7,完蛋了,我們需要對這個類進行修改了!這完全違背了 開閉原則 !
- 其實這還好說,關鍵這是一家店,如果是千千萬萬的專賣店,讓你修改,怕別怕?
於是,我們對流程進行優化, 封裝變化 ,你明白我的意思吧,把那個該死的總是改變的地方封裝起來!
public class SimpleCarFactory { public Car createCar(String type) { Car car = null; if (type.equals("A4")) { car = new AduiA4(); } else if (type.equals("A6")) { car = new AudiA6(); } else if (type.equals("A8")) { car = new AudiA8(); } return car; } }
public class CarStore{ SimpleCarFactory scf; public CarStore(SimpleCarFactory scf){ this.scf = scf; } public Car saleCar(String type){ car = scf.createCar(type); // 售前檢查 car.check(); // 汽車出庫 car.outStore(); return car; } }
現在,你隨便加方法,隨便開新店,所有事情都由我這個簡單汽車工廠來做,對於專賣店來說,再也不用頭痛這些事了!
不過簡單方法模式 缺點 也很明顯,
- 如果工廠類集中了所以的建立邏輯,當有複雜的多層次等級結構時,所有的業務邏輯都在這個工廠類中實現,這個類會變的異常複雜。
- 什麼時候它不能工作了, 整個系統 都會受到影響。
- 不符合開閉原則,一旦新增新的物件,就不得不對新的產品進行修改。
三、工廠方法模式(拆分汽車工廠為例)
為了解決簡單工廠模式的缺點,於是產生了工廠方法模式。工廠方法模式中將物件例項化延遲到子類中完成,建立物件只需要新增新的子類工廠就可以。
現在要滿足如下需求,奧迪公司決定在中國和德國建立汽車工廠,在中國生產國產奧迪,在德國生產進口奧迪。
先對簡單工廠進行改裝
public class SimpleCarFactory { public Car createCar(String type,String area) { Car car = null; if ("imports".equals(area)){ if ("A4".equals(type)) { car = new ImportsAduiA4(); } else if ("A6".equals(type)) { car = new ImportsAudiA6(); } else if ("A8".equals(type)) { car = new ImportsAudiA8(); } } else if("domestics".equals(area)){ if ("A4".equals(type)) { car = new DomesticsAduiA4(); } else if ("A6".equals(type)) { car = new DomesticsAudiA6(); } else if ("A8".equals(type)) { car = new DomesticsAudiA8(); } } return car; } }
問題很明顯
- 當奧迪公司決定在美國在開一家工廠,那又要對這個類進行修改,如果新增新的產品還是要修改。
- 這是一個高度依賴工廠類,本工廠依賴於所有的汽車產品型別,這是不符合依賴倒置原則的——要依賴抽象,不要依賴具體類。無論是高層元件(汽車工廠)還是低層元件(各型別汽車)都要依賴抽象。
因此我們需要改進工廠方法,讓其依賴汽車介面而不是所有的奧迪汽車。
UML:
抽象工廠類
public abstract class AbstractCarFactory { public abstract Car createCar(String type); }
進口工廠
public class ImportsCarFactory extends AbstractCarFactory{ @Override public Car createCar(String type) { if ("A4".equals(type)) { car = new ImportsAduiA4(); } else if ("A6".equals(type)) { car = new ImportsAudiA6(); } else if ("A8".equals(type)) { car = new ImportsAudiA8(); } return car; } }
國產工廠
public class DomesticsCarFactory extends AbstractCarFactory{ @Override public Car createCar(String type) { if ("A4".equals(type)) { car = new DomesticsAduiA4(); } else if ("A6".equals(type)) { car = new DomesticsAudiA6(); } else if ("A8".equals(type)) { car = new DomesticsAudiA8(); } return car; } }
客戶端
public class Test { public static void main(String[] args) { AbstractCarFactory factory01 = new ImportsCarFactory(); AbstractCarFactory factory02 = new DomesticsCarFactory(); Car importsA4 = factory01.create("A4"); Car domesticsA4 = factory02.create("A4"); } }
這就是工廠方法模式,無論是高層元件還是低層元件都是依賴於抽象的Car介面,當我們需要進行擴充套件時,例如建立美國工廠,只需要建立一個新的工廠類繼承抽象工廠類就可以。
四、優缺點
優點:
- 更符合開-閉原則:新增一種產品時,只需要增加相應的具體產品類和相應的工廠子類即可。
- 符合單一職責原則:每個具體工廠類只負責建立對應的產品。
- 符合依賴倒置原則:無論底層元件還是高層元件都依賴於抽象而不是具體實現。
缺點:
- 新增新的產品時,除了產品類外,還要提供與之對應的具體工廠實現,系統類的個數將成倍增加,在一定程度上增加了系統的複雜度;同時,有更多的類需要編譯和執行,會給系統帶來一些額外的開銷。
- 雖然保證了工廠方法內的對修改關閉,但對於使用工廠方法的類,如果要更換另外一種產品,仍然需要修改例項化的具體工廠類。(例如一家專賣店不賣國產,改賣進口了,那就得對店鋪進行修改)
- 一個具體工廠只能建立一種型別的具體產品,進口工廠無法建立國產產品,國產工廠也不能建立進口汽車。
五、總結
本文對工廠方法模式進行了簡單描述。工廠方法模式可以說是簡單工廠模式的進一步抽象和拓展,在保留了簡單工廠的封裝優點的同時,讓擴充套件變得簡單,讓繼承變得可行,增加了多型性的體現。掌握工廠方法模式的重點在於理解將例項化推遲到子類中實現和底層元件與高層元件都依賴於抽象。
本文首發於 cdream 個人部落格
歡迎轉載,轉載請註明出處!