1. 程式人生 > >【設計模式】HeadFirst設計模式(四):工廠模式

【設計模式】HeadFirst設計模式(四):工廠模式

     設計模式要求我們不應該針對實現程式設計,是為了降低耦合度,提高可維護性。當程式中出現“new”的時候,就證明程式在例項化一個具體類,所以用的是實現,而不是介面。如果程式碼綁著具體的類會導致程式碼更加脆弱,缺乏彈性。比如,需要建立一“個雞蛋餅”這個物件,首先需要建立一個餅,然後建立一個雞蛋,再然後把雞蛋攤在餅上邊,然後給餅翻翻,幾分鐘後就出爐了....(有點餓)。在這種情況下,新物件的建立是一個過程,如果我們需要在這個餅上邊抹點辣椒醬,那肯定需要對類進行修改,違反了“對擴充套件開放,對修改關閉”的設計原則。這樣就出現了一個問題:

     如何讓客戶直接構造出物件的例項,而不用在乎構造物件例項的具體細節?

(就比如說,我想吃雞蛋餅,直接就能買到,不需要知道這個雞蛋餅如何做出來的)

具體實現:

首先,我們需要知道,工廠模式包含了三種:簡單工廠(靜態工廠)、工廠方法、抽象工廠。但是簡單工廠並不屬於GOF的23種設計模式

下邊會用製作Pizza的例子來對三個模式進行說明:

假設你有一個Pizza店,並且你有很多種型別的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;
	}
根據引數傳入型別type的不同,例項化不同的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

抽象工廠(放在了下一篇)