我們平時都在使用new來創建一個新的對象,但是仔細想想,我們這樣公開的初始化一個對象,會經常造成耦合問題。今天我們要說的這個工廠模式便可以幫我們從復雜的依賴中解脫出來。
一、披薩的故事
我們現在需要建一個能夠制作披薩的程序,這里面有不同口味的披薩,同時還有披薩的所有步驟,這里給出了四個步驟(准備,烘烤,切割,打包)。
1、思考“new”
當我們使用“new”時,是在實例化一個具體的類,當有一群相關的具體類時,通常會寫出這樣的代碼:
/**
* 披薩店類
*/
public class PizzaStore {
SimplePizzaFactory pizzaFactory;
public PizzaStore(SimplePizzaFactory pizzaFactory) {
this.pizzaFactory = pizzaFactory;
}
/**
* 點披薩
*
* @param type 類型
* @return Pizza 披薩
*/
Pizza orderPizza(String type) {
Pizza pizza;
if ("cheese".equals(type)) {
pizza = new CheesePizza();
} else if ("greek".equals(type)) {
pizza = new GreekPizza();
} else if ("pepperoni".equals(type)) {
pizza = new PepperoniPizza();
} else {
return null;
}
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
這里有一群要實例化的具體類,究竟實例化哪個類,要在運行時由一些條件決定。但是當程序有變化或者需要擴展的時候,就必須重新打開這段代碼進行檢查和修改,這會使我們的代碼更加脆弱,缺乏彈性,容易出現bug。
但是,“new”真的有錯嗎,答案當然是否定啦。真正有錯的是“變化”。上面的設計沒有遵循“開閉原則”!
下面我們用最簡單的封裝方式,將披薩的創建從 PizzaStore 類中移除,創建另一個類來實現披薩的創建,從而使披薩的創建過程對於 PizzaStore 來說,是不可見的。
二、簡單工廠
1、簡單工廠下的披薩定做流程UML
上面說的這種做法便是簡單工廠方法,我們先上代碼。
2、實現:
抽象披薩類:
/** * 披薩抽象類 */ public abstract class Pizza { /** * 披薩的名字 */ protected String name; /** * 准備 */ public void prepare() { System.out.println(name + " preparing"); } /** * 烘烤 */ public void bake() { System.out.println(name + " baking"); } /** * 切割 */ public void cut() { System.out.println(name + " cutting"); } /** * 打包 */ public void box() { System.out.println(name + " boxing"); } }
芝士披薩
/** * 芝士披薩 類 */ public class CheesePizza extends Pizza { public CheesePizza() { this.name = "CheesePizza"; } }
希臘披薩
/** * 希臘披薩 類 */ public class GreekPizza extends Pizza { public GreekPizza() { this.name = "GreekPizza"; } }
臘香腸披薩
/** * 臘香腸披薩 類 */ public class PepperoniPizza extends Pizza { public PepperoniPizza() { this.name = "PepperoniPizza"; } }
簡單披薩工廠類
/** * 簡單披薩工廠類 */ public class SimplePizzaFactory { /** * 創建披薩 * @param type 類型 * @return Pizza */ public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(); } else if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(); } else { return null; } return pizza; } }
改良后的披薩店
/** * 披薩店類 */ public class PizzaStore { SimplePizzaFactory pizzaFactory; public PizzaStore(SimplePizzaFactory pizzaFactory) { this.pizzaFactory = pizzaFactory; } /** * 點披薩 * * @param type 類型 * @return Pizza 披薩 */ Pizza orderPizza(String type) { /*Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(); } else if ("greek".equals(type)) { pizza = new GreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(); } else { return null; }*/ Pizza pizza = pizzaFactory.createPizza(type); pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }
3、定義簡單工廠
首先,要強調一點,簡單工廠不是設計模式的一種!!!而更像是一種編程習慣,並且在實際應用中經常被用到。
4、現在呢,由於披薩賣的很好,披薩店的老板准備開加盟店,但是,這時候問題就來了,每個加盟店所在的地域不盡相同,因此,設計出的披薩肯定更加符合當地人的口味,因此即便同樣名字的披薩,可能因為地域的不同,口味會不相同。比如,對於(芝士披薩)CheesePizza,紐約有紐約的風味,芝加哥有芝加哥的風味。這邊引出了我們下面要說的工廠方法。
三、工廠方法
1、下面我們用工廠方法來實現披薩店程序。這里,我們將 PizzaStore(披薩店)做成抽象類,將創建披薩交給子類去做。UML類圖如下:
2、代碼實現:
我們將披薩的子類重新定義,變為紐約風味的某某披薩,芝加哥風味的某某披薩
芝加哥風味的芝士披薩
/** * 芝加哥風味的芝士披薩 */ public class ChicagoStyleCheesePizza extends Pizza { public ChicagoStyleCheesePizza() { this.name = "ChicagoStyleCheesePizza"; } }
芝加哥風味的希臘披薩
/** * 芝加哥風味的希臘披薩 */ public class ChicagoStyleGreekPizza extends Pizza { public ChicagoStyleGreekPizza() { this.name = "ChicagoStyleGreekPizza"; } }
芝加哥風味的臘香腸披薩
/** * 芝加哥風味的臘香腸披薩 */ public class ChicagoStylePepperoniPizza extends Pizza { public ChicagoStylePepperoniPizza() { this.name = "ChicagoStylePepperoniPizza"; } }
紐約風味的芝士披薩
/** * 紐約風味的芝士披薩 */ public class NYStyleCheesePizza extends Pizza { public NYStyleCheesePizza() { this.name = "NYStyleCheesePizza"; } }
紐約風味的希臘披薩
/** * 紐約風味的希臘披薩 */ public class NYStyleGreekPizza extends Pizza { public NYStyleGreekPizza() { this.name = "NYStyleGreekPizza"; } }
紐約風味的臘香腸披薩
/** * 紐約風味的臘香腸披薩 */ public class NYStylePepperoniPizza extends Pizza { public NYStylePepperoniPizza() { this.name = "NYStylePepperoniPizza"; } }
披薩店
/** * 披薩店 抽象類 */ public abstract class PizzaStore { /** * 點披薩 * @param type 類型 * @return Pizza */ public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } return null; } /** * 創建披薩 由子類決定創建什么Pizza * @param type 類型 * @return Pizza */ public abstract Pizza createPizza(String type); }
芝加哥風味的披薩店
/** * 芝加哥風味的披薩店 */ public class ChicagoStylePizzaStore extends PizzaStore { /** * 創建具體的芝加哥風味的披薩 * @param type 類型 * @return Pizza */ @Override public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new ChicagoStyleCheesePizza(); } else if ("greek".equals(type)) { pizza = new ChicagoStyleGreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new ChicagoStylePepperoniPizza(); } else { return null; } return pizza; } }
紐約風味的披薩店
/** * 紐約風味的披薩店 */ public class NYStylePizzaStore extends PizzaStore { /** * 創建具體的紐約風味的披薩 * * @param type 類型 * @return Pizza */ @Override public Pizza createPizza(String type) { Pizza pizza; if ("cheese".equals(type)) { pizza = new NYStyleCheesePizza(); } else if ("greek".equals(type)) { pizza = new NYStyleGreekPizza(); } else if ("pepperoni".equals(type)) { pizza = new NYStylePepperoniPizza(); } else { return null; } return pizza; } }
模擬顧客點單
/** * 模擬顧客點單 */ public class TestFactoryMethod { public static void main(String[] args) { PizzaStore nyStylePizzaStore = new NYStylePizzaStore(); PizzaStore chicagoStylePizzaStore = new ChicagoStylePizzaStore(); //分別點紐約 芝加哥風味的cheese披薩 nyStylePizzaStore.orderPizza("cheese"); chicagoStylePizzaStore.orderPizza("cheese"); } }
運行結果
從運行結果,工廠方法完美的解決了我么上面的問題,如果我們要刪除或者新加某個風味的披薩,只需改對應的類就行了,減少了代碼改動對整體的影響范圍。從工廠方法的實現,我們可以看出,產品類和創建者類時平級的。這里用到了依賴倒置原則。
3、定義:
工廠方法模式:定義了一個創建對象的接口,但由子類確定要實例化的類是哪一個,工廠方法讓類把實例化推遲到子類。
4、優點:
①、一個調用者想創建一個對象,只要知道其名稱就可以了。
②、擴展性高,如果想增加一個產品,只要擴展一個工廠類就可以。
③、屏蔽產品的具體實現,調用者只關心產品的接口。
5、缺點:
每次增加一個產品時,都需要增加一個具體類和對象實現工廠,使得系統中類的個數成倍增加,在一定程度上增加了系統的復雜度,同時也增加了系統具體類的依賴。這並不是什么好事。
6、使用場景:
①、日志記錄器:記錄可能記錄到本地硬盤、系統事件、遠程服務器等,用戶可以選擇記錄日志到什么地方。
②、數據庫訪問,當用戶不知道最后系統采用哪一類數據庫,以及數據庫可能有變化時。
③、設計一個連接服務器的框架,需要三個協議,"POP3"、"IMAP"、"HTTP",可以把這三個作為產品類,共同實現一個接口。
7、注意事項:
作為一種創建類模式,在任何需要生成復雜對象的地方,都可以使用工廠方法模式。有一點需要注意的地方就是復雜對象適合使用工廠模式,而簡單對象,特別是只需要通過 new 就可以完成創建的對象,無需使用工廠模式。如果使用工廠模式,就需要引入一個工廠類,會增加系統的復雜度。
8、現在呢,問題又出現了。我們建了紐約風味的披薩類,又建了芝加哥風味的披薩類,但是,我們想一下,他們的不同的地方無非就是准備階段所做的工作不一樣。所以我們想只建一個披薩類,至於是紐約風味的還是芝加哥風味的,我們可以通過抽象工廠來實現,下面讓我們看一下抽象工廠模式。
四、抽象工廠方法
1、我們通過建立不同的披薩生產工廠,從未讓准備階段不相同,進而實現不同風味的披薩。我們的披薩店在創建披薩的時候,不需要關心創建的是啥,只需要將具體的工廠類傳入即可。
Pizza類 將准備方法變為抽象方法,具體的實現在子類中實現
/** * 披薩抽象類 */ public abstract class Pizza { /** * 披薩的名字 */ protected String name; /** * 准備 */ public abstract void prepare(); /** * 烘烤 */ public void bake() { System.out.println(name + " baking"); } /** * 切割 */ public void cut() { System.out.println(name + " cutting"); } /** * 打包 */ public void box() { System.out.println(name + " boxing"); } }
披薩原料准備工廠 抽象類 建立了一個create方法,模擬“准備”方法所干的事,並簡化
/** * 披薩原料准備工廠 */ public interface PizzaIngredientFactory { String create(); }
芝加哥風味的原料工廠 將 chicagoStyle 返回,模擬准備各種原料
/** * 芝加哥風味的原料工廠 */ public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory { @Override public String create() { return "chicagoStyle"; } }
紐約風味的原料工廠
/** * 紐約風味的原料工廠 */ public class NYPizzaIngredientFactory implements PizzaIngredientFactory { @Override public String create() { return "nyStyle"; } }
芝士披薩 初始化的時候,傳入一個工廠,並在“准備”方法中獲取相應的名字
/** * 芝士披薩 類 */ public class CheesePizza extends Pizza { PizzaIngredientFactory ingredientFactory; public CheesePizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "CheesePizza"; System.out.println(this.name + " preparing"); } }
希臘披薩
/** * 希臘披薩 類 */ public class GreekPizza extends Pizza { PizzaIngredientFactory ingredientFactory; public GreekPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "GreekPizza"; System.out.println(this.name + " preparing"); } }
臘香腸披薩
/** * 臘香腸披薩 類 */ public class PepperoniPizza extends Pizza { PizzaIngredientFactory ingredientFactory; public PepperoniPizza(PizzaIngredientFactory ingredientFactory) { this.ingredientFactory = ingredientFactory; } @Override public void prepare() { this.name = ingredientFactory.create() + "PepperoniPizza"; System.out.println(this.name + " preparing"); } }
PizzaStore
/** * 披薩店 抽象類 */ public abstract class PizzaStore { /** * 點披薩 * @param type 類型 * @return Pizza */ public Pizza orderPizza(String type) { Pizza pizza = createPizza(type); if (pizza != null) { pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); } return null; } /** * 創建披薩 由子類決定創建什么Pizza * @param type 類型 * @return Pizza */ public abstract Pizza createPizza(String type); }
芝加哥風味的披薩店 在創建披薩的時候,生成一個芝加哥原料工廠,並用這個工廠來創建具體的披薩
/** * 芝加哥風味的披薩店 */ public class ChicagoStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { PizzaIngredientFactory chicagoPizzaIngredientFactory = new ChicagoPizzaIngredientFactory(); Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(chicagoPizzaIngredientFactory); } else if ("greek".equals(type)) { pizza = new GreekPizza(chicagoPizzaIngredientFactory); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(chicagoPizzaIngredientFactory); } else { return null; } return pizza; } }
紐約風味的披薩店
/** * 紐約風味的披薩店 */ public class NYStylePizzaStore extends PizzaStore { @Override public Pizza createPizza(String type) { PizzaIngredientFactory nyPizzaIngredientFactory = new NYPizzaIngredientFactory(); Pizza pizza; if ("cheese".equals(type)) { pizza = new CheesePizza(nyPizzaIngredientFactory); } else if ("greek".equals(type)) { pizza = new GreekPizza(nyPizzaIngredientFactory); } else if ("pepperoni".equals(type)) { pizza = new PepperoniPizza(nyPizzaIngredientFactory); } else { return null; } return pizza; } }
模擬顧客點單
/** * 模擬顧客點單 */ public class TestAbstractFactory { public static void main(String[] args) { PizzaStore nyStylePizzaStore = new NYStylePizzaStore(); nyStylePizzaStore.orderPizza("cheese"); } }
運行結果:
通過抽象工廠,我們將程序進一步抽象,來達到我們想要的結果。
2、定義:
抽象工廠模式:提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類、