場景描述:我們正在設計一個披薩店,因爲我們對披薩的操作有一套固有的流程,比如切割,裝盒等,但是我們有着非常多的披薩類型,風味不同,所以如何設計針對不同口味創建不同的披薩就成了整個程序的難點。
簡單工廠
爲了增加程序的彈性,並且避免頻繁修改代碼,我們將變化的部分拿出去,創建了一個簡單工廠類。
public class SimplePizzaFactory {
public Pizza createPizza(String type){
Pizza pizza = null;
if (type.equals("cheese")){
pizza = new CheesePizza();
}else if (type.equals("pepperoni")){
pizza = new PepperoniPizza();
}else if (type.equals("clam")){
pizza = new ClamPizza();
}else if (type.equals("veggie")){
pizza = new VeggiePizza();
}
return pizza;
}
}
利用靜態方法定義一個簡單的工廠,這是很常見的技巧,常被稱爲靜態工廠。爲何使用靜態方法?因爲不需要使用創建對象的方法來實例化對象。getInstance()。
這樣我們的披薩店代碼就呼之欲出了。
public class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory){
this.factory = factory;
}
public Pizza orderPizza(String type){
Pizza pizza;
pizza = factory.createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
簡單工廠其實並不是一個設計模式,反而比較像是一種編程習慣。有些開發人員的確是把這個編程習慣誤認爲是“工廠模式”。
下面是這個簡單工廠的類圖:
工廠方法
新的問題:當我們出現新的加盟店時,如紐約和芝加哥,它們的口味不太相同,這樣我們就需要創建不同區域的特有工廠,然後再根據對應的口味生產特定的披薩。不過我們能夠看出來,不同的工廠中,通過特定口味生產披薩這個步驟都是相同的,只不過最後生產的具體披薩不同而已,所以我們是否可以將其抽象出來呢?
我們嘗試抽象披薩店的代碼:
public abstract class PizzaStore {
public Pizza orderPizza(String type){
Pizza pizza;
pizza = createPizza(type);
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
public abstract Pizza createPizza(String type);
}
然後針對不同地域去實現特定的製作方法
所有工廠模式都用來封裝對象的創建。工廠方法模式(Factory Method Pattern)通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。
讓我們來看一下通過這種方式來下單的流程如何:
工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。工廠方法讓類把實例化推遲到子類。
工廠方法模式能夠封裝具體類型的實例化。而所謂的“決定”,並不是指模式允許子類本身在運行時做決定,而是指編寫創建者類時,不需要知道實際創建的產品是哪一個。
我們看一下工廠方法模式的類圖:
再對比我們的披薩店,可以更加直觀的感受
簡單工廠把全部的事情,在一個地方都處理完了,然而工廠方法卻是創建一個框架,讓子類決定要如何實現。簡單工廠的做法,可以將對象的創建封裝起來,但是簡單工廠不具備工廠方法的彈性,因爲簡單工廠不能變更正在創建的產品。
抽象工廠
讓我們再看一個依賴性非常強的實現。
上圖看出了,依賴過多,所以會導致非常大的弊端,每次新增種類,不僅需要修改代碼,還造成了過多的依賴。
設計原則:要依賴抽象,不要依賴具體類。
這個原則說明了:不能讓高層組件依賴底層組件,而且,不管高層或底層組件,“兩者”都應該依賴於抽象。
這就是“依賴倒置原則”(Dependency Inversion Principle)。
下圖可以更加清晰地看出這句話的含義。
下面的指導方針,能幫你避免在OO設計中違反依賴倒置原則:
- 變量不可以持有具體類的引用。
- 不要讓類派生自具體類。
- 不要覆蓋基類中已實現的方法。
當然只是儘量達到這個原則,而不是隨時都要遵守這個原則。
新的需求:我們又新增了新的需求,我們擁有不同地域的加盟店,並且它們使用的原料各不相同,而我們想要控制原料,而不是由他們自己提供原料,所以我們就需要一個新的設計來管理整個原料家族。
我們嘗試建造一個工廠來創建所有原料。
public interface PizzaIngredientFactory {
public Dough createDough();
public Sauce createSauce();
public Cheese createCheese();
public Veggies[] createVeggies();
public Pepperoni createPepperoni();
public Clams createClams();
}
然後我們創建每個地域的工廠,如紐約的
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Dough createDough() {
return new ThinCrustDough();
}
@Override
public Sauce createSauce() {
return new MarinaraSauce();
}
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Veggies[] createVeggies() {
Veggies veggies[] = {new Garlic(), new Onion(), new Mushroom()};
return veggies;
}
@Override
public Pepperoni createPepperoni() {
return new SlicedPepperoni();
}
@Override
public Clams createClams() {
return new FreshClams();
}
}
這時我們的披薩變爲抽象的了。
public abstract class Pizza {
public String name;
public Dough dough;
public Sauce sauce;
public Veggies veggies[];
public Cheese cheese;
public Pepperoni pepperoni;
public Clams clams;
public abstract void prepare();
public void bake(){
System.out.println("Bake for 25 minutes at 350");
}
public void cut(){
System.out.println("Cutting the pizza into diagonal slices");
}
public void box(){
System.out.println("Place pizza in official PizzaStore box");
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
}
此時我們的不同口味披薩不再需要地域性了,因爲地域性已經在原料中體現了。
public class CheesePizza extends Pizza {
PizzaIngredientFactory ingredientFactory;
public CheesePizza(PizzaIngredientFactory ingredientFactory){
this.ingredientFactory = ingredientFactory;
}
public void prepare(){
System.out.println("Preparing " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
這時我們再實現不同區域的披薩店
public class NYPizzaStore extends PizzaStore {
public Pizza createPizza(String item){
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (item.equals("cheese")){
pizza = new CheesePizza(ingredientFactory);
pizza.setName("New York Style Cheese Pizza");
}// 省略其他類型pizza
return pizza;
}
}
現在我們看一下一個披薩訂單都經歷了什麼
上述模式就是一種新的工廠模式。
抽象工廠模式提供一個接口,用於創建相關或依賴對象的家族,而不需要明確指定具體類。
我們看一下它的類圖:
比對一下我們的披薩店,更直觀的理解一下
總結:
整個工廠方法模式,只不過就是通過子類來創建對象。用這種做法,客戶只需要知道他們所使用的抽象類型就可以了,而由子類來負責決定具體類型。換句話說,它將客戶從具體類型中解耦。
而抽象工廠模式,提供一個用來創建一個產品家族的抽象類型,這個類型的子類定義了產品被產生的方法。要想使用這個工廠,必須先實例化它,然後將它傳入一些針對抽象類型所寫的代碼中。換句話說,它將客戶從所使用的實際具體產品中解耦。
當你需要創建產品家族和想讓製造的相關產品集合起來時,你可以使用抽象工廠。
而當你需要把你的客戶代碼從需要實例化的具體類中解耦,或者如果你目前還不知道將來需要實例化哪些具體類時,就可以使用工廠方法。