一、簡單披薩店
我們在對象村開了家簡單的披薩店,下面是披薩店製作披薩的代碼:
public class PizzaStore {
Pizza orderPizza(String type) {
Pizza pizza = null;
//根據類型創建不同種類的披薩
if (type.equals("cheese")) {
pizza = new CheesePizza();
}else if (type.equals("greek")) {
pizza = new GreekPizza();
}
//加工披薩步驟
pizza.prepare();
pizza.cut();
pizza.box();
}
}
爲什麼不要使用具體實例化(WHY)
代碼綁着具體類會導致代碼更耦合。當使用"new"時,就是在實例化一個具體類,一旦有變化或擴展,比如增加新的類型的披薩,就必須重新打開這段代碼進行檢查和修改。
如何使用簡單工廠替代具體實例化(HOW)
需要遵循"對擴展開放,對修改關閉”的原則解耦合,很明顯地,根據類型創建不同種類的披薩是變化的部分,而加工披薩是不會改變的部分。要把前者移到另一個類中,我們稱之爲工廠類。一旦有了工廠SimplePizzaFactory,披薩店orderPizza()就變成客戶,需要披薩的時候就叫工廠做一個,自己只需要調用prepare()、cut()、box()等加工方法。
建立一個簡單的披薩工廠:
public class SimplePizzaFactory {
//只做一件事:創建披薩
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
}else if (type.equals("greek")) {
pizza = new GreekPizza();
}
return pizza;
}
}
這樣做似乎只是把問題轉移到另一個類,但是工廠類可能有多個客戶,譬如宅急送類、披薩店菜單類等都會調用工廠類。當以後改變時,只需要修改工廠類一處。
重構pizzaStore類:
public class PizzaStore {
SimplePizzaFactory simplePizzaFactory;
//PizzaStore的構造器,參數爲工廠
public PizzaStore(SimplePizzaFactory simplePizzaFactory) {
this.simplePizzaFactory = simplePizzaFactory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
//使用工廠類來創建披薩,取代new具體實例化的方法。
pizza = simplePizzaFactory.createPizza(type);
pizza.prepare();
pizza.cut();
pizza.box();
return pizza;
}
}
什麼是簡單工廠(WHAT)
簡單工廠其實不是一個設計模式,而是一種編程習慣,它不是工廠模式(Factory Pattern)。披薩店的類圖如下:
二、披薩加盟店
簡單披薩店經營有成,現在大家都希望加盟,在自家附近開店,身爲公司CEO,你希望把控加盟店的質量,都用你的代碼。但是各地有風味差異,所以需要建立一個框架。
如何解決風味差異的問題(HOW)
有個做法可讓披薩製作侷限於PizzaStore類,同時加盟店又可以自由製作該區域風味。所要做的就是把createPizza()放回到PizzaStore類,不過要把它設置爲“抽象方法”,然後每個加盟店創建其子類。類圖及代碼如下:
public abstract class PizzaStorePlus {
public Pizza orderPizza(String type) {
Pizza pizza;
//createPizza()方法從工廠類中移回PizzaStore
pizza = createPizza(type);
pizza.prepare();
pizza.cut();
pizza.box();
return pizza;
}
//抽象的
abstract Pizza createPizza(String type);
}
//紐約分店
public class NYPizzaStore extends PizzaStorePlus {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new NYCheesePizza();
}else if (type.equals("greek")) {
pizza = new NYGreekPizza();
}
return pizza;
}
}
//芝加哥分店
public class ChicagoPizzaStore extends PizzaStorePlus {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new ChicagoCheesePizza();
}else if (type.equals("greek")) {
pizza = new ChicagoGreekPizza();
}
return pizza;
}
}
披薩同樣需要定義爲抽象的,以便不同風味的具體子類繼承:
public abstract class Pizza {
String name;
//佐料
List toppings = new ArrayList();
void prepare() {
System.out.println("準備烘焙:" + name);
System.out.println("加佐料:");
for(int i=0;i<toppings.size();i++) {
System.out.println(" "+toppings.get(i));
}
}
void cut() {
System.out.println("切披薩");
}
void box() {
System.out.println("裝披薩");
}
public String getName() {
return name;
}
}
public class NYCheesePizza extends Pizza{
public NYCheesePizza() {
name = "紐約Cheese披薩";
toppings.add("意大利高級奶酪");
}
}
public class ChicagoCheesePizza extends Pizza {
public ChicagoCheesePizza() {
name = "芝加哥Cheese披薩";
toppings.add("意大利白乾奶酪");
}
void cut() {
System.out.println("切成正方形");
}
}
我們來訂購披薩:
public class PizzaDrive {
public static void main(String[] args) {
NYPizzaStore nyPizzaStore = new NYPizzaStore();
ChicagoPizzaStore chicagoPizzaStore = new ChicagoPizzaStore();
Pizza pizza = nyPizzaStore.orderPizza("cheese");
System.out.println("訂了個:"+pizza.getName());
pizza = chicagoPizzaStore.orderPizza("cheese");
System.out.println("訂了個:"+pizza.getName());
}
}
超類不用管細節,通過實例化正確的披薩子類,子類會自行照料一切。運行結果如下:
什麼是工廠方法模式(WHAT)
工廠方法模式的構成:
工廠方法模式由創建者類(PizzaStore及其子類)和產品類(Pizza及其子類)構成。工廠方法模式通過讓子類決定該創建的對象是什麼,來達到將對象創建的過程封裝的目的。
工廠方法模式的定義:
工廠方法模式定義了一個創建對象的接口,但由子類決定要實例化的類是哪一個。所謂的“決定”是指在編寫創建者類時,不需要知道實際創建的產品是哪一個,選擇使用哪個子類,自然就決定了實際創建的產品是什麼。工廠方法模式類圖如下:
依賴倒置原則(WHAT)
工廠方法模式其實遵循了依賴倒置原則。
如下,披薩店直接創建各種披薩對象,於是完全依賴於它們
我們需要遵循依賴倒置原則:要依賴抽象,不要依賴具體類。它是一個著名的OO設計原則。應用工廠方法模式後,高層組件(披薩店)和低層組件(各種披薩)都依賴Pizza抽象類,這就是倒置,類圖如下:
依賴倒置原則的指導方針:
變量不可以持有具體類的引用。如:使用new,可以使用工廠來替代它
不要讓類派生自具體類。請派生自抽象類。
不要覆蓋基類已實現的方法。
三、新增原料工廠
爲什麼要使用抽象工廠模式(WHY)
有一些加盟店使用低價原料來增加利潤,我們必須採取手段控制原料,以免損害品牌。你打算建造一家生產原料的工廠。原料包括醬料和芝士等。開始爲原料工廠定義一個接口:
public interface PizzaIngredientFactory {
public Cheese createCheese();
public Clams createClams();
}
紐約和芝加哥的原料工廠實現它:
public class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Cheese createCheese() {
return new ReggianoCheese();
}
@Override
public Clams createClams() {
return new FreshClams();
}
}
public class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
@Override
public Cheese createCheese() {
return new ChicagoCheese();
}
@Override
public Clams createClams() {
return new ChicagoClams();
}
}
原料改變,將prepare()方法聲明爲抽象方法,重構披薩:
public abstract class Pizza {
String name;
Cheese cheese;
Clams clams;
//佐料
List toppings = new ArrayList();
abstract void prepare();
void cut() {
System.out.println("切披薩");
}
void box() {
System.out.println("裝披薩");
}
public String getName() {
return name;
}
public String toString() {
}
}
我們曾經寫過NYCheesePizza和ChicagoCheesePizza,它們唯一的差別在於區域性原料不同,這個讓原料工廠處理差異就行,所以我們只需設計CheesePizza和ClamPizza:
public class CheesePizza extends Pizza {
PizzaIngredientFactory factory;
//要製作披薩,需要工廠提供原料。所以每個披薩類都需要從構造器參數得到一個工廠來製造原料。
public CheesePizza(PizzaIngredientFactory factory) {
this.factory = factory;
}
@Override
void prepare() {
System.out.println("Preparing " + name);
cheese = factory.createCheese();
}
}
public class ClamsPizza extends Pizza {
PizzaIngredientFactory factory;
//要製作披薩,需要工廠提供原料。所以每個披薩類都需要從構造器參數得到一個工廠來製造原料。
public ClamsPizza(PizzaIngredientFactory factory) {
this.factory = factory;
}
@Override
void prepare() {
System.out.println("Preparing " + name);
clams = factory.createClams();
}
}
加盟店需要使用對應原料工廠的披薩,我們將加盟店與當地原料工廠搭上關係:
public class NYPizzaStorePlus extends PizzaStorePlus {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("紐約起司披薩");
}else if (type.equals("clams")) {
pizza = new ClamsPizza(ingredientFactory);
pizza.setName("紐約蛤蜊披薩");
}
return pizza;
}
}
public class ChicagoPizzaStorePlus extends PizzaStorePlus {
@Override
Pizza createPizza(String type) {
Pizza pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
if (type.equals("cheese")) {
pizza = new CheesePizza(ingredientFactory);
pizza.setName("芝加哥起司披薩");
}else if (type.equals("clams")) {
pizza = new ClamsPizza(ingredientFactory);
pizza.setName("芝加哥蛤蜊披薩");
}
return pizza;
}
}
訂購披薩:
public class PizzaDrivePlus {
public static void main(String[] args) {
NYPizzaStorePlus nyPizzaStorePlus = new NYPizzaStorePlus();
ChicagoPizzaStorePlus chicagoPizzaStorePlus = new ChicagoPizzaStorePlus();
Pizza pizza = nyPizzaStorePlus.orderPizza("cheese");
System.out.println("訂了個:"+pizza.getName());
pizza = chicagoPizzaStorePlus.orderPizza("cheese");
System.out.println("訂了個:"+pizza.getName());
}
}
結果如下:
什麼是抽象工廠模式(WHAT)
抽象工廠模式提供一個抽象的接口,用於創建一組產品,而不需要知道實際產出的具體產品是什麼。另外抽象工廠的方法經常以工廠方法的方式實現。類圖如下:
工廠方法模式與抽象工廠模式異同(WHERE)
同:都是把應用程序從特定實現中解耦。
異:工廠方法使用繼承;抽象工廠使用組合。