一、概述 工廠方法模式定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。 二、解決問題 通常我們需要一個對象的時候,會想到使用new來創建對象 Tea tea = new MilkTea(); //使用了介面,代碼更有彈性,體現設計原則“對介面編程,而不是對 ...
一、概述
工廠方法模式定義了一個創建對象的介面,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
二、解決問題
通常我們需要一個對象的時候,會想到使用new來創建對象
Tea tea = new MilkTea(); //使用了介面,代碼更有彈性,體現設計原則“對介面編程,而不是對實現編程”
當我們需要多個對象的時候,”對介面編程“的原則似乎還能派上用場
Tea tea; if("milk".equals(type)){ tea = new MilkTea(); }else if("coffee".equals(type)){ tea = new CoffeeTea(); }else if("lemon".equals(type)){ tea = new LemonTea(); }
這裡也體現了“對介面編程”的好處,運行時決定要實例化哪個對象,讓系統具備了彈性,但對於擴展性方面,我們就不敢恭維了。看以上代碼,當我們要新增對象或者要擴展時,不得不打開這份代碼進行檢查和修改。通常這樣的修改過的代碼會造成部分系統更難維護和更新,而且也容易犯錯。
為了有良好的擴展性,我們想到了另外一個設計原則”把變化的代碼從不變化的代碼中分離出來”。
假設我們要開一家燒餅店,我們每天會做各種口味的燒餅出售,做燒餅的程式包括準備原材料、和麵、烘烤、切片、裝盒。
//燒餅店 public class ShaobingStore { public Shaobing orderShaobing(String type){ Shaobing shaobing = null; if("onion".equals(type)){ //洋蔥燒餅 shaobing = new OnionShaobing(); }else if("sour".equals(type)){ //酸菜燒餅 shaobing = new SourShaobing(); }else if("beef".equals(type)){ //牛肉燒餅 shaobing = new BeefShaobing(); } //以上代碼會發生改變,當洋蔥燒餅不再出售時,我們會把創建洋蔥的代碼刪除,我們可能會新增新口味的燒餅 else if("pork".equals(type)){ //牛肉燒餅 shaobing = new PorkShaobing(); } //對於製作燒餅的程式中,以下這些步驟是不變的 if(shaobing != null){ shaobing.prepare(); shaobing.cut(); shaobing.bake(); shaobing.box(); } return shaobing; } }
對於上面代碼,我們使用”分離變化“的原則,把創建燒餅代碼封裝到一個類中,我們把它叫做工廠類,裡面專門一個方法用來創建燒餅,如下所示:
package factorymethod.pattern; public class SimpleShaobingFactory { public Shaobing createShaobing(String type){ Shaobing shaobing = null; if("onion".equals(type)){ //洋蔥燒餅 shaobing = new OnionShaobing(); }else if("sour".equals(type)){ //酸菜燒餅 shaobing = new SourShaobing(); }else if("beef".equals(type)){ //牛肉燒餅 shaobing = new BeefShaobing(); } return shaobing; } }
改進後的燒餅店如下:
package factorymethod.pattern; //燒餅店 public class ShaobingStore { public Shaobing orderShaobing(String type){ Shaobing shaobing = null; shaobing = factory.createShaobing(type); //對於製作燒餅的程式中,以下這些步驟是不變的 if(shaobing != null){ shaobing.prepare(); shaobing.cut(); shaobing.bake(); shaobing.box(); } return shaobing; } SimpleShaobingFactory factory; public ShaobingStore(SimpleShaobingFactory factory){ this.factory = factory; } }
如上圖所示,不管以後燒餅的口味怎麼變,燒餅店的代碼都不用變了,要擴展或者修改燒餅,我們只要更改創建燒餅的工廠類,也就是SimpleShaobingFactory 類,這就解開了燒餅店和燒餅的耦合,體現了“對擴展開放,對修改關閉”的設計原則。
其實上面的改進方案使用了一個沒有被真正冠名的設計模式“簡單工廠模式”,其類圖如下所示:
從上面的類圖來看,如果我們的燒餅店開在不同的地方,不同地方對洋蔥燒餅,酸菜燒餅要求的口味不一樣,北方人喜歡放辣椒,南方人喜歡清淡的,我們的燒餅店該怎麼開呢?這就是工廠方法模式要幫我們解決的問題,工廠方法模式讓類把實例化推遲到子類,讓子類決定實例化的類是哪一個,將產品的“實現”從“使用”中解耦出來,讓系統同時具備了彈性和擴展性。簡單工廠不夠彈性,不能改變正在創建的產品(同一種類型的只有一個,拿洋蔥燒餅來說,全國各地的口味一樣,沒有辣與不辣的區分了)
三、結構類圖
四、成員角色
抽象創建者(Creator):定義了創建對象模板,實現了所有操縱產品的方法,除了工廠方法。具體創建者必須繼承該類,實現工廠方法。
具體創建者(ConcreteCreator):繼承抽象創建者,實現工廠方法,負責創建產品對象。
抽象產品(Product):定義了產品的共用資源,提供給子類繼承使用,某些方法可以做成抽象方法,強制子類實現。
具體產品(ConcreteProduct):繼承自抽象產品,實現父類的抽象方法,也可以覆蓋父類的方法,從而產生各種各類的產品。
五、應用實例
下麵還是以開燒餅店為例,介紹如何在廣州和長沙開燒餅店,賣適合當地風味的燒餅,而且燒餅的種類和名稱一樣。
首先抽象燒餅店,也就是Creator
package factorymethod.pattern; public abstract class ShaobingStore { public Shaobing orderShaobing(String type){ Shaobing shaobing = createShaobing(type); shaobing.prepare(); shaobing.cut(); shaobing.bake(); shaobing.box(); return shaobing; } //未實現的工廠方法 public abstract Shaobing createShaobing(String type); }
第二步,創建抽象燒餅,也就是Product
package factorymethod.pattern; public abstract class Shaobing { //燒餅名稱 public String name; //燒餅用的配料 public String sauce; //麵團 public String dough; public void prepare(){ System.out.println("Prepareing " + name); //和麵 System.out.println("Kneading dough..."); //加配料 System.out.println("加配料:" + sauce); } //烤燒餅 public void bake(){ System.out.println("Bake for 25 minutes at 350C"); } //切麵團 public void cut(){ System.out.println("Cutting the dough into fit slices"); } //打包 public void box(){ System.out.println("Place shaobing into official box"); } }
第三步、創建廣州風味的燒餅(加番茄醬的洋蔥燒餅和牛肉燒餅),對應ConcreteProduct
package factorymethod.pattern; public class GZOnionShaobing extends Shaobing{ public GZOnionShaobing(){ name = "廣州的洋蔥燒餅"; //配料 sauce = "番茄醬"; } }
package factorymethod.pattern; public class GZBeefShaobing extends Shaobing{ public GZBeefShaobing(){ name = "廣州的牛肉燒餅"; //配料 sauce = "番茄醬"; } }
第四步、創建長沙風味的燒餅(加辣椒醬的洋蔥燒餅和牛肉燒餅),對應ConcreteProduct
package factorymethod.pattern; public class CSOnionShaobing extends Shaobing{ public CSOnionShaobing(){ name = "長沙洋蔥燒餅"; //配料 sauce = "辣椒醬"; } }
package factorymethod.pattern; public class CSBeefShaobing extends Shaobing{ public CSBeefShaobing(){ name = "長沙牛肉燒餅"; //配料 sauce = "辣椒醬 "; } }
第五步、創建廣州燒餅店,對應ConcreteCreator
package factorymethod.pattern; //廣州燒餅店 public class GZShaobingStore extends ShaobingStore{ @Override public Shaobing createShaobing(String type) { Shaobing shaobing = null; if("onion".equals(type)){ shaobing = new GZOnionShaobing(); }else if("beef".equals(type)){ shaobing = new GZBeefShaobing(); } return shaobing; } }
第六步、創建長沙燒餅店,對應ConcreteCreator
package factorymethod.pattern; //長沙燒餅店 public class CSShaobingStore extends ShaobingStore{ @Override public Shaobing createShaobing(String type) { Shaobing shaobing = null; if("onion".equals(type)){ shaobing = new CSOnionShaobing(); }else if("beef".equals(type)){ shaobing = new CSBeefShaobing(); } return shaobing; } }
第七步、測試售出名字相同但風味不一樣的燒餅
package factorymethod.pattern; public class TestShaobingStore { public static void main(String[] args){ //在廣州開一個燒餅店 ShaobingStore gzStore = new GZShaobingStore(); //售出一個洋蔥燒餅 gzStore.orderShaobing("onion"); System.out.println("----------------------"); //在長沙開一個燒餅店 ShaobingStore csStore = new CSShaobingStore(); //售出一個洋蔥燒餅 csStore.orderShaobing("onion"); } }
運行結果:
六、工廠方法特有的設計原則
如果我們之間在燒餅店中直接實例化一個燒餅,這種設計師依賴具體類的,類圖如下:
這種依賴具體類設計,擴展性、彈性、維護性都比較差。如果將實例化的代碼獨立出來,使用工廠方法,我們將不再依賴具體類了,請看如下類圖:
這就是我們要講的依賴倒置原則:要依賴抽象,不要依賴具體類。用依賴倒置原則設計的系統,使得對象的實現從使用中解耦,對象的使用是在Creator,實現卻在ConcreteCreator中,Creator只有Product的引用,Creator與ConcreteProduct松耦合,這種設計很強的擴展性、彈性和可維護性。
設計中使用以來倒置原則方法:
1、變數不可以持有具體類的引用(就是不能使用new,使用工廠方法)
2、不要讓類派生自具體類(繼承抽象或者實現介面)
3、不要覆蓋基類中已實現的方法
七、優點和缺點
1、優點
(1)、符合“開閉”原則,具有很強的的擴展性、彈性和可維護性。擴展時只要添加一個ConcreteCreator,而無須修改原有的ConcreteCreator,因此維護性也好。解決了簡單工廠對修改開放的問題。
(2)、使用了依賴倒置原則,依賴抽象而不是具體,使用(客戶)和實現(具體類)松耦合。
(3)、客戶只需要知道所需產品的具體工廠,而無須知道具體工廠的創建產品的過程,甚至不需要知道具體產品的類名。
2、缺點
(1)、一個具體產品對應一個類,當具體產品過多時會使系統類的數目過多,增加系統複雜度。
(1)、每增加一個產品時,都需要一個具體類和一個具體創建者,使得類的個數成倍增加,導致系統類數目過多,複雜性增加。
(2)、對簡單工廠,增加功能修改的是工廠類;對工廠方法,增加功能修改的是客戶端。
八、使用場合
1、當需要一個對象時,我們不需要知道該對象所對應的具體類,只要知道哪個具體工廠可以生成該對象,實例化這個具體工廠即可創建該對象。
2、類的數目不固定,隨時有新的子類增加進來,或者是還不知道將來需要實例化哪些具體類。
3、定義一個創建對象介面,由子類決定要實例化的類是哪一個;客戶端可以動態地指定工廠子類創建具體產品。