4 Factory Pattern(工廠模式)
前言:工廠模式是為了解決new的問題
案例分析:
REQ1:Vander作為pizza店的老板,具有一整套制作pizza的流程,準備食材、烘焙、切片、包裝,隨著pizza種類的漸漸增加,設計如下:
public class PizzaStore {
public Pizza pizza;
public Pizza orderPizza(String type) {
Pizza pizza = new Pizza();
if(type.equals("cheese")) {
pizza = new CheesePizza();
} else if(type.equals("greek")) {
pizza = new GreekPizza();
} else if(type.equals("pepperoni")) {
pizza = new Pepperoni();
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
分析:隨著pizza店的發展,后期發現要制作越來越多的pizza,而且種類也是超級多,多達幾十種pizza,而且有些舊類型的pizza,幾乎沒人點,greek和pepperoni pizza就從菜單上撤離了,而增加了FruitsPizza、HawaiiPizza、DurianPizza,所以上面orderPizza()方法就會頻繁地改動,經常改動導致pizza店的訂單系統經常處在維護狀態,這非常不利于接單,很明顯,實例化某些具體類,將使得orderPizza()方法出問題,而且這違反了“開放-關閉原則”,這個方法就無法對修改關閉了;但是現在已經知道哪些是會改變的,哪些是不會改變的部分,接下來用封裝來解決上面的問題。
解決方法1:Vander 又開始改進設計了
將創建pizza的代碼封裝起來轉移到另一個對象中,然后orderPizza()方法專門負責pizza的制作工作。new pizza這件事專門交給其他類去負責。
重新修改PizzaStore的代碼:
public class PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
簡單工廠的代碼:
public class SimplePizzaFactory {
public Pizza producePizza(String type) {
Pizza pizza = new Pizza();
if(type.equals("cheese")) {
pizza = new CheesePizza();
} else if(type.equals("greek")) {
pizza = new GreekPizza();
} else if(type.equals("pepperoni")) {
pizza = new Pepperoni();
}
return pizza;
}
}
現在相當于,每次修改SimplePizzaFactory的代碼的時候,可以暫時不對線上的版本進行影響,修改完SimplePizzaFactory之后再替換掉線上的SimplePizzaFactory就OK了。而orderPizza()方法相當于變成了SimplePizzaFactory的使用者,它使用pizza工廠來完成pizza的new操作。
下面定義簡單工廠,實際上簡單工廠不算做一個設計模式,更多地是一種編程習慣,我們會經常用到它,所以也是非常重要的。
REQ2:Vander的Pizza店名氣非常大,吸引了很多大腕來加盟,但是由于區域的差異,每個加盟店都可能需要作出不同風格的pizza,例如四川的希望水果Pizza里面有辣椒,廣東的則希望水果Pizza里面多一點菠蘿,這受到了開店地區當地人口味的影響。
首先要完成加盟店,所以PizzaStore的SimpleFactory就成了一些地區PizzaFactory,如GuangDongPizzaFactory、SiChuanPizzaFactory。然后他們各自使用各自的Pizza類,例如廣東的創造廣東類型的Pizza-GDPizza;四川的創作自己類型的Pizza-SCPizza,然后他們各自再覆蓋原來的pizza里面的prepare、bake、cut、box等方法,他們自創了自己的流程,而且box的時候還用了別的商家的盒子,這完全就是打著Vander Pizza店的牌子在亂搞嘛。
問題來了,怎樣才能加強質量把控,又不失彈性,讓這些地區Pizza店可以依照自己的口味去。
解決方法2:工廠方法模式
Vander思前想后就是想不到合適的辦法,這時候他只能去請教Panda大師,Panda大師說這個可以用工廠方法解決,首先要給加盟Pizza點使用框架,先從PizzaStore開始改造:
由于想控制加盟店制作Pizza類中的bake、cut、box,不能讓這些加盟店隨便亂弄,也不能讓它們使用別的公司的包裝,需要把控整個制作過程,把PizzaStore的producePizza方法設為抽象類,讓GDPizzaStore和SCPizzaStore去繼承,它們在生產完Pizza之后只能調用父類的orderPizza方法,繼續完成準備、烘焙、切片、包裝。讓我們來看看現在的改造吧:
public abstract class PizzaStore {
public final Pizza orderPizza(String type) {
Pizza pizza = producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract Pizza producePizza(String type);
}
public abstract class Pizza {
protected String name;
//面團類型
protected String dough;
//醬料
protected String sauce;
//佐料
protected ArrayList<String> toppings = new ArrayList<String>(10);
public void prepare() {
System.out.println("Preparing " + name);
System.out.println(" Tossing dough ...");
System.out.println(" Adding sauce ...");
System.out.println(" Adding toppings: ");
for(int i=0; i<toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
public void bake() {
System.out.println("Bake for 25 mins at 350℃");
}
public void cut() {
System.out.println("Cutting the pizza into diagonal slices");
}
public void box() {
System.out.println("Boxing the pizza");
}
public String getName() {
return name;
}
}
public class GDStyleFruitsPizza extends Pizza {
public GDStyleFruitsPizza() {
name = "GuangDong Style Fruits Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("GuangDong Delicious Cheese");
toppings.add("GuangDong Pipeapple");
}
}
public class SCStyleFruitsPizza extends Pizza {
public SCStyleFruitsPizza() {
name = "SiChuan Style Fruits Pizza";
dough = "Thick Crust Dough";
sauce = "Marinara Sauce";
toppings.add("SiChuan Delicious Cheese");
toppings.add("SiChuan Pepper");
}
}
public class GDPizzaStore extends PizzaStore {
@Override
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new GDStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new GDStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new GDStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new GDStyleHawaiiPizza();
}
return pizza;
}
}
public class SCPizzaStore extends PizzaStore {
@Override
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new SCStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new SCStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new SCStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new SCStyleHawaiiPizza();
}
return pizza;
}
}
說明:工廠方法用來處理對象的創建,并將這樣的行為封裝在子類中,這樣客戶程序中關于超類的代碼就和子類對象創建代碼解耦了。也就是說不管以后有多少加盟店,父類PizzaStore都不會去改變了,任由子類PizzaStore天翻地覆,PizzaStore都以不變應萬變。工廠方法模式通過讓子類決定該創建的對象是什么,來達到將對象創建的過程封裝的目的。
Vander就納悶了,我之前用的簡單工廠難道不能實現嗎,然后Vander就想Panda大師這么干到底有什么優勢,接著Vander發現他也能給像Panda那樣做到把控整個控制過程。實際上關鍵的代碼就是將PizzaStore的orderPizza()方法寫成final,讓子類加盟店都無法修改整個制作過程,接著讓Pizza類的box()方法也寫成final,這樣子子類Pizza就無法使用別的商家的包裝了。接著Vander給出了他的實現。
以下是主要的代碼:
public class GDPizzaStore extends PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public GDPizzaStore(SimplePizzaFactory simplePizzaFactory) {
super(simplePizzaFactory);
}
}
public class GDPizzaFactory extends SimplePizzaFactory{
public Pizza producePizza(String type) {
Pizza pizza = null;
if(type.equals("cheese")) {
pizza = new GDStyleCheesePizza();
} else if(type.equals("fruits")) {
pizza = new GDStyleFruitsPizza();
} else if(type.equals("durian")) {
pizza = new GDStyleDurianPizza();
} else if(type.equals("Hawaii")) {
pizza = new GDStyleHawaiiPizza();
}
return pizza;
}
}
public class PizzaStore {
public SimplePizzaFactory simplePizzaFactory;
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public final Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
public abstract class SimplePizzaFactory {
public abstract Pizza producePizza(String type);
}
說明:從代碼上看來,其實確實跟工廠方法模式差不多,只是把producePizza方法放到了簡單工廠而已,但是這么操作最后在運行的時候,必須要實例化相應的工廠,例如GDPizzaStore需要傳入GDPizzaFactory,則需要實例化GDPizzaFactory,并且還不能傳錯,傳錯了就會導致最后廣東的Pizza店賣的確是其它工廠造出來的Pizza。
下面比對一下工廠方法模式和簡單工廠是怎么造出Pizza的。
簡單工廠的制作Pizza方法:
public class Main {
public static void main(String[] args) {
GDPizzaFactory gdFactory = new GDPizzaFactory();
GDPizzaStore gdPizzaStore = new GDPizzaStore(gdFactory);
gdPizzaStore.orderPizza("fruits");
SCPizzaFactory scFactory = new SCPizzaFactory();
SCPizzaStore scPizzaStore = new SCPizzaStore(scFactory);
scPizzaStore.orderPizza("fruits");
}
}
工廠方法的制作Pizza方法:
public class Main {
public static void main(String[] args) {
GDPizzaStore gdPizzaStore = new GDPizzaStore();
Pizza gdFruitsPizza1 = gdPizzaStore.orderPizza("fruits");
SCPizzaStore scPizzaStore = new SCPizzaStore();
Pizza scfruitsPizza2 = scPizzaStore.orderPizza("fruits");
}
}
最后Vander發現原來他的方法需要先實例化GDPizzaFactory,然后將Factory作為參數放進GDPizzaStore里面,Vander就想,既然都是GDPizzaFactory對應GDPizzaStore,SCPizzaFactory對應SCPizzaStore,那我直接在GDPizzaStore里面實例化GDPizzaFactory,SCPizzaStore里面實例化SCPizzaFactory不就好了。接著又進行了一輪改造:
public class GDPizzaStore extends PizzaStore {
public GDPizzaStore() {
simplePizzaFactory = new GDPizzaFactory();
}
}
public class PizzaStore {
protected SimplePizzaFactory simplePizzaFactory;
public final Pizza orderPizza(String type) {
Pizza pizza = simplePizzaFactory.producePizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
說明:最后雖然在運行時的調用是一樣的,但是這里相當于直接讓GDPizzaStore和GDPizzaFactory直接綁定在一起了,也增加了代碼的耦合度。而且每次多開一個加盟店,都需要實現PizzaStore和PizzaFactory多一次,確實也沒多大必要。
REQ3:由于Pizza大家已經吃膩了,所以Vander為了招來更多的客戶,想擴展菜單了,現在不只是賣Pizza了,現在Pizza店學習必勝客的方式,還賣雞翅、漢堡并且還出售各種獨家飲料。那么怎么擴展接口呢?在PizzaStore基類中繼續添加produceFiredChicken()、produceBurger()、produceBeverage(),這么做的后果就是所有的加盟店都得跟著做改變,但是有些加盟店Pizza生意做得火熱,覺得根本沒必要添加這么多種類進去。這時候Vander想到了他之前實現的SimpleFactory2,就是將簡單工廠編程“抽象工廠”,讓子類工廠去完成產品的創建,這樣做的好處就是加盟店的PizzaStore都可以不用動,而且把這些食物的創建都放到了一個大工廠去做這么一件事情了。
下面是Vander的設計:
這個設計圖看起來有一定的復雜度,但是其實就是在SimpleFactory2上的PizzaFactory改為了FoodFactory添加了幾個生產其他產品的方法,本來只有一個Pizza的抽象類,現在多了Beverage、Burger、FiredChicken這幾個抽象類,相當于現在FoodFactory是一個大工廠來生產各種各樣的產品。可能你會發現這FoodFactory里面實際上不就是工廠方法模式嘛,沒錯!就是這樣相當于FoodFactory里面生產各種各樣的食物,但是生產的食物是什么樣的Pizza、什么樣的炸雞,都是由子類做決定的。
實際上SimpleFactory2已經不算是簡單工廠了,簡單工廠應該是更單純一點的,通過一個含參的方法來實例化產品類。SimpleFactory2經過改進之后已經成為了抽象工廠模式了。
抽象工廠允許客戶使用抽象的接口來創建一組相關的產品,而不需要知道實際產出的具體產品是什么,這樣一來,客戶就從具體的產品中被解耦了。上面這段話通俗地說,就是像子類工廠完成Pizza的創建,然后PizzaStore在orderPizza()方法中直接是拿基類Pizza類來進行prepare、bake、cut、box等操作,而不用理會是哪種口味哪個區域的Pizza。從而達到了PizzaStore與具體的Pizza類的解耦。
下面我們梳理一下整個過程,首先剛開始PizzaStore依賴于具體的Pizza類,如下圖所示:
而且隨著Pizza類型的增加,依賴將會越來越多,代碼里面減少具體的類的依賴是必須要做的,接下來我們看看我們后面的實現:
這樣就依賴抽象的類Pizza。(遵循了依賴抽象而不是具體的類的原則)
我們發現高層組件(PiizaStore)和底層組件(具體的Piizza子類)都依賴于Pizza的抽象類。我們一般進行設計的時候,首先設計PizzaStore,PizzaStore要能生產出很多具體的不同類型的Pizza,其實這時候你可以倒過來想,從具體的Pizza想起,發現應該先抽象出一個Pizza基類,然后這個Pizza基類直接給PizzaStore用,然后就發現這個抽象的Pizza基類就像橋梁一樣連接著PizzaStore和具體的Pizza類。這里面就包含了“依賴倒置原則”,即在從上往下的設計的同時,不妨先想想能不能從下往上。
下面有三個指導方針來避免違反“依賴倒置原則”:
1、變量不可以持有具體類的引用。(如果需要用new,用工廠代替)
2、不要讓類派生自具體類。(如果派生具體類了,你就會依賴具體類,請派生一個抽象)
3、不要覆蓋基類中已實現的方法(基類已經實現了,說明具有通用性,否則請設為抽象)
最后的最后,我們又來總結我們現在現有的設計模式武器。
面向對象基礎
抽象、封裝、多態、繼承
六大原則
設計原則一:封裝變化
設計原則二:針對接口編程,不針對實現編程。
設計原則三:多用組合,少用繼承。
設計原則四:為交互對象之間的松耦合設計而努力。
設計原則五:對擴展開放,對修改關閉。
設計原則六:依賴抽象,不要依賴于具體的類 。
模式
工廠方法模式:定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
抽象工廠模式:提供一個接口,用于創建相關對象或者是依賴對象的家族,而不需要明確指定具體的類(說白了就是一系列同類型的工廠方法集合)。