【設計模式】HeadFirst設計模式(四):工廠模式
設計模式要求我們不應該針對實現程式設計,是為了降低耦合度,提高可維護性。當程式中出現“new”的時候,就證明程式在例項化一個具體類,所以用的是實現,而不是介面。如果程式碼綁著具體的類會導致程式碼更加脆弱,缺乏彈性。比如,需要建立一“個雞蛋餅”這個物件,首先需要建立一個餅,然後建立一個雞蛋,再然後把雞蛋攤在餅上邊,然後給餅翻翻,幾分鐘後就出爐了....(有點餓)。在這種情況下,新物件的建立是一個過程,如果我們需要在這個餅上邊抹點辣椒醬,那肯定需要對類進行修改,違反了“對擴充套件開放,對修改關閉”的設計原則。這樣就出現了一個問題:
如何讓客戶直接構造出物件的例項,而不用在乎構造物件例項的具體細節?
具體實現:
首先,我們需要知道,工廠模式包含了三種:簡單工廠(靜態工廠)、工廠方法、抽象工廠。但是簡單工廠並不屬於GOF的23種設計模式
下邊會用製作Pizza的例子來對三個模式進行說明:
假設你有一個Pizza店,並且你有很多種型別的Pizza,那麼你會這樣寫程式碼:
根據引數傳入型別type的不同,例項化不同的Pizza。Pizza orderPizza(String type){ Pizza pizza; if(type.equals("chesse")){ pizza = new CheesePizza(); }else if(type.equals("greek")){ pizza = new GreekPizza(); }else if(type.equals("pepperoni")){ pizza = new PepperoniPizza(); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; }
但是,你的競爭物件已經在選單中加入了其它流行風味Pizza:ClamPizza(蛤蜊披薩)、VeggiePizza(素食披薩)。如果你想要趕上他們你就必須在自己的選單中加入這些風味的披薩,而GreekPizza(希臘披薩)因為賣的不好所以去掉:
很明顯的,orderPizza()方法不能夠使得對修改關閉,但是我們已經知道哪些會改變,哪些不會改變,就可以使用封裝了。
一、簡單工廠模式:
要把建立Pizza的程式碼移動到另一個物件中,由這個新物件專職建立Pizza:
問題:這麼做似乎是把問題搬到另一個物件罷了?似乎沒有什麼好處?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; } }
答:SimplePizzaFactory可以有許多的客戶,雖然目前僅僅只有orderPizza()方法是它的客戶,但是在以後的擴充套件過程中可能會有很多個客戶(但是如果把createPizza()這個方法放入到orderPizza()方法中的話,它只可能為orderPizza()一個服務)。所以,把建立Pizza的程式碼包裝進一個類後,當以後實現改變的話,只需要修改這個類即可。我們正需要做的就是把例項化的過程,從客戶的程式碼中刪除。
缺點:因為是靜態的,不能夠通過繼承來改變建立方法的行為。
下邊,我們把其它的類寫出來:
PizzaStore:是SimplePizzaFactory工廠類的“客戶”,PizzaStor通過工廠類取得Pizza的例項
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;
}
}
Pizza:工廠的產品
abstract public class Pizza {
String name;
String dough;
String sauce;
ArrayList<String> toppings = new ArrayList<String>();
public String getName() {
return name;
}
public void prepare() {
System.out.println("Preparing " + name);
}
public void bake() {
System.out.println("Baking " + name);
}
public void cut() {
System.out.println("Cutting " + name);
}
public void box() {
System.out.println("Boxing " + name);
}
public String toString() {
StringBuffer display = new StringBuffer();
display.append("---- " + name + " ----\n");
display.append(dough + "\n");
display.append(sauce + "\n");
for (int i = 0; i < toppings.size(); i++) {
display.append(toppings.get(i) + "\n");
}
return display.toString();
}
}
CheesePizza:public class CheesePizza extends Pizza {
public CheesePizza() {
name = "Cheese Pizza";
dough = "Regular Crust";
sauce = "Marinara Pizza Sauce";
toppings.add("Fresh Mozzarella");
toppings.add("Parmesan");
}
}
VeggiePizza:
public class VeggiePizza extends Pizza {
public VeggiePizza() {
name = "Veggie Pizza";
dough = "Crust";
sauce = "Marinara sauce";
toppings.add("Shredded mozzarella");
toppings.add("Grated parmesan");
toppings.add("Diced onion");
toppings.add("Sliced mushrooms");
toppings.add("Sliced red pepper");
toppings.add("Sliced black olives");
}
}
ClamPizza:
public class ClamPizza extends Pizza {
public ClamPizza() {
name = "Clam Pizza";
dough = "Thin crust";
sauce = "White garlic sauce";
toppings.add("Clams");
toppings.add("Grated parmesan cheese");
}
}
寫一個Main類:PizzaTestDrive
public class PizzaTestDrive {
public static void main(String[] args) {
SimplePizzaFactory factory = new SimplePizzaFactory();
PizzaStore store = new PizzaStore(factory);
Pizza pizza = store.orderPizza("cheese");
System.out.println("We ordered a " + pizza.getName() + "\n");
pizza = store.orderPizza("veggie");
System.out.println("We ordered a " + pizza.getName() + "\n");
}
}
把UML類圖畫出來了:
二、工廠方法模式
特點:定義了一個建立物件的介面,但由子類決定要例項化的類是哪一個。工廠方法讓類把例項化推遲到子類
現在,大家都希望能夠在自家附近加盟你開的披薩店(其實就是想用你的招牌在他們那裡開店啦)。但是,每個區域都會有差異,每家加盟店都想要提供不同風味的比薩(比方說紐約、芝加哥、加州),你想這樣做:
//紐約風味的素食Pizza
NYPizzaFactory nyFactory = new NYPizzaFactory();
PizzaStore nyStore = new PizzaStore(nyFactory);
nyStore.orderPizza("Veggie");
//芝加哥風味的素食Pizza
ChicagePizzaFactory chicagoFactory = new ChicagePizzaFactory();
PizzaStore chicagoStore = new PizzaStore(chicagoFactory);
chicagoStore.orderPizza("Veggie");
問題:但是,在推廣你的方法的時候,別的加盟店的確是採用你的工廠建立比薩,但是其他部分卻開始此採用他們自創的流程:烘烤的做法有差異、不要切片、使用其他廠商的盒子等等。
有一種做法可以讓比薩製作活動侷限於PizzaStore類,而同時又能讓這些加盟店依然可以自由的製作本地區域的風味:
把createPizza()方法放回到PizzaStore中,不過要把它設定成“抽象方法”:(原本是由一個物件負責所有具體類的例項化,現在通過對PizzaStor做一些轉變,變成由一群子類來負責例項化)
宣告一個工廠類:
public abstract class PizzaStore {
//把createPizza()方法設定成抽象的,由子類做決定
abstract Pizza createPizza(String item);
public Pizza orderPizza(String type) {
Pizza pizza = createPizza(type);
System.out.println("--- Making a " + pizza.getName() + " ---");
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
然後宣告兩個具體工廠類:NYPizzaStore、ChicagoPizzaStore:public class NYPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new NYStyleCheesePizza();
} else if (item.equals("veggie")) {
return new NYStyleVeggiePizza();
} else if (item.equals("clam")) {
return new NYStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new NYStylePepperoniPizza();
} else return null;
}
}
public class ChicagoPizzaStore extends PizzaStore {
Pizza createPizza(String item) {
if (item.equals("cheese")) {
return new ChicagoStyleCheesePizza();
} else if (item.equals("veggie")) {
return new ChicagoStyleVeggiePizza();
} else if (item.equals("clam")) {
return new ChicagoStyleClamPizza();
} else if (item.equals("pepperoni")) {
return new ChicagoStylePepperoniPizza();
} else return null;
}
}
我們需要建立一個Pizza實體類:
public abstract class Pizza {
String name; //名稱
String dough; //麵糰型別
String sauce; //醬料
ArrayList<String> toppings = new ArrayList<String>(); //作料
void prepare() {
System.out.println("準備 " + name);
System.out.println("揉麵團...");
System.out.println("新增醬料...");
System.out.println("新增作料: ");
for (int i = 0; i < toppings.size(); i++) {
System.out.println(" " + toppings.get(i));
}
}
void bake() {
System.out.println("烘烤25分鐘");
}
void cut() {
System.out.println("把Pizza對角切片");
}
void box() {
System.out.println("把Pizza裝盒子");
}
public String getName() {
return name;
}
}
然後需要一些具體的子類,下邊定義兩個子類:紐約風味的芝士披薩(NYStyleCheesePizza)、芝加哥風味的芝士披薩(ChicageStyleCheesePizza)public class NYStyleCheesePizza extends Pizza {
public NYStyleCheesePizza() {
name = "NY Style Sauce and Cheese Pizza";
dough = "Thin Crust Dough";
sauce = "Marinara Sauce";
toppings.add("Grated Reggiano Cheese");
}
}
public class ChicagoStyleCheesePizza extends Pizza {
public ChicagoStyleCheesePizza() {
name = "Chicago Style Deep Dish Cheese Pizza";
dough = "Extra Thick Crust Dough";
sauce = "Plum Tomato Sauce";
toppings.add("Shredded Mozzarella Cheese");
}
//可以覆蓋cut()方法
void cut() {
System.out.println("Cutting the pizza into square slices");
}
}
我們把UML類圖畫出來:
如果我們需要一個紐約風味的芝士披薩,我們應該怎麼做:
(1) 需要一個紐約比薩店:PizzaStore nyPizzaStore = new NYPizzaStore();
(2) 下訂單:nyPizzaStore.orderPizza("cheese");
(3) orderPizza()方法呼叫createPizza()方法:Pizza pizza = createPizza("cheese");
(4) 最後經過:pizza.prepare()、pizza.bake()、pizza.cut()、pizza.box()才能完成Pizza
抽象工廠(放在了下一篇)