開發之路(設計模式四:工廠模式上)

本期咱們要介紹一個能讓你烘烤本身的OO設計的一種模式,工廠模式

請問除了使用new之外,你還有其餘創造對象的方法嗎?若是你說沒有,那麼和我一塊兒好好學習下這個模式吧。你會認識到每每實例化不該該老是公開進行,也會認識到初始化常常形成的「耦合」問題。java


在以前的模式當中,我有介紹過一個原則,咱們不該該針對實現編程。但當看到「new」時,就會想到「具體」,不管是 A a=new A()仍是 A a=new B(),都其實在實例化一個具體類,因此的確用的是實現,而不是接口,或者再好比在以前「模擬鴨子」當中
Duck d = new MallardDuck(); Duck是一個接口,但MallardDuck仍是得創建具體類呀,OK若這樣想那麼至少明白了,代碼與具體類「捆綁」是耦合,缺乏彈性一種表現。數據庫

那這時就有小夥伴說了:「但總要有建立對象的時候把,Java中只提供了new關鍵字鍵對象,還能有別的嗎?」 固然new關鍵字自己沒有問題,這是Java原生部分,其實阻礙咱們的是「改變」,針對接口編程,能夠隔離掉當「改變」來了之後的許多問題,若是代碼是針對接口(這裏我在此強調,接口是一個大概念,不僅僅interface是接口,抽象類,類也能是一個「接口」)而寫,那麼經過多態,它能夠與任何新類實現該接口。可是我我的也以爲彷佛有問題:當代碼大量使用具體類時,會自找麻煩,由於當一旦要求增長某些內容時,就必須改變代碼。也就是說這樣代碼也不是「對修改關閉」(前期內容)了,想要新的具體類型來拓展代碼,必須從新修改並覆蓋它。編程

說了那麼多,具體咋寫代碼呢,不能紙上談兵把?OK,我具體來寫個實例。假設你有家披薩店。設計模式

注:設計模式須要反覆屢次練習,方可靈活運用,前期能夠先模仿。

一開始這樣寫(這裏我引用書中筆記)
圖片描述app

固然咱們還要傳入類型來製做不一樣口味的披薩,以下圖
圖片描述框架

可是你知道嗎?這樣設計是會有很大壓力的,好比你要添加新的口味披薩,或者取出掉某些賣的很差風味的披薩的話。要這麼寫,不管增長新披薩或者刪除掉原有披薩你都要到這裏操做,上面部分會進行修改,而下面部分準備原料,烘烤,切片,裝盤,操做都是不改變的,只有上面打算作什麼口味披薩的這個動做會改變。ide

注:請區別出哪些是會改變的,哪些不會改變

圖片描述

咱們作的改進,封裝建立對象的代碼,要把建立披薩的代碼轉移到另外一個對象中,由這個對象專職建立披薩,這個新對象只幹這一件事,如圖
圖片描述學習

沒錯,咱們稱呼爲它「工廠」。
在JavaWeb當中有個持久層框架Hibernate,其一個核心接口「工廠」(SessionFactory)就是用到工廠模式,負責初始化hibernate,是數據源的代理,負責建立Session對象,一個數據庫對象只需配置一個SessionFactory,如有多個也能夠配置多個。測試

工廠(factory)處理建立對象的細節,一旦有了Factory,orderPizza()就變成了,此對象的客戶。當須要什麼類型披薩時,就叫披薩工廠作一個。如今orderPizza()方法只關心從工廠獲得一個披薩,而這個披薩實現了Pizza接口,因此能夠調用準備材料,烘烤,切片,裝盤方法。spa

那咱們來建立個簡單工廠。
圖片描述

可能有小夥伴提問:「感受只是把以前會被大量修改的問題移到另外一個對象裏而已」
注:SimlePizzaFactory會有不少客戶,目前咱們看到orderPizza()方法是它客戶,可是,可能還有PizzaShopMenu(披薩店菜單),xxxxPizzaShop(各類不一樣風格披薩店)等等會利用這個工廠來取得披薩里的內容,總而言之,SimlePizzaFactory會有許多客戶。因此,把建立披薩的代碼包裝進一個類,當之後實現改變時,只需修改此類便可。我正要把具體實例化的過程,在客戶代碼中刪除。

此時咱們寫一個披薩「工廠」的客戶類(PizzaStore)
圖片描述

以上就是簡單工廠模式的內容了,
注:簡單工程嚴格來講不是一個設計模式,更像是一種編程習慣,但因爲常常被使用,因此有時候不少開發人員誤認爲「工廠模式」。

下面是簡單工程的類圖。(請注意理解)
圖片描述

注:
再次提醒:所謂的「實現一個接口」並不必定表示「寫一個(interface)類,而後利用implements關鍵詞來實現某個Java接口」。「實現一個接口」泛指「實現某個超類型(能夠是類或接口)的某個方法」。

你覺得這篇文章寫道這裏就結束了?哈哈,別忘了個人口號是啥~~

既然咱們有了本身的披薩店,咱們固然但願將它作大作強,咱們開始加盟披薩店。
圖片描述

這裏咱們建立對應的「工廠」對象。
圖片描述

但這又有一個問題了,難道你能聽任加盟店無論嗎?這樣各個加盟店就按照本身的方式,烘烤的作法不一樣,不要切片,使用其餘廠商的盒子,若是可以創建一個框架,把加盟店和建立披薩捆綁在一塊兒的同時又保持必定的彈性。

有個作法能夠把披薩製做活動侷限在客戶(PizzaStore)類中,而同時又能讓這些加盟店依然能夠自由的建立該區域披薩,就是將createPizza方法放回PizzaStore內,將其設置爲「抽象方法」,讓後每一個區域的加盟店建立它的子類,就是說二者之間造成依賴關係。
圖片描述
讓咱們詳細一點。
圖片描述

從orderPizza()方法觀點來看,此方法在抽象父類PizzaStore裏定義,但具體實現仍是在不一樣子類中。面對每一個不一樣子類,父類的orderPizza()方法其實並不知道哪一個子類將實際上製做披薩。

orderPizza()方法對Pizza對象指向,(準備材料,烘烤,切片,裝盒)但因爲Pizza對象是抽象的,orderPizza()對象也並不知道哪些實際參與的具體類,換句話說,這就是解耦。(WTF?這就是解耦,不少時候每每初學設計模式的人還不能作到很敏感,包括我本身,須要長時間不斷學習)

OK,咱們這時候開一家加盟店咯。
圖片描述
注:超類(父類)PizzaStore中orderPizza()方法,並不知道正要建立什麼披薩,它只知道製做披薩有四個步驟。。。

如今咱們建立工廠方法,以前都是new,對象負責具體類的實例化,如今咱們經過PizzaStore作一個小轉變(其實在這裏,我我的感受說他是工廠方法感受怪怪的)
圖片描述

咱們再來鞏固下工廠方法(這裏我放出個人筆記截圖)
圖片描述

咱們來寫下購買披薩流程
1:首先,客戶們須要取得披薩店實例。客戶須要實例化一個NYStylePizzaStore或ChicagoStylePizzaStore
2:有了各自的PizzaStore訂單,客戶們分別調用orderPizza()方法,並傳入他們所喜好的比薩類型(芝士,素食)。
3:orderPizza調用createPizza()建立披薩,紐約風味披薩(NYStylePizzaStore)或者芝加哥風味披薩(ChicagoStylePizzaStore)。
4:orderPizza()並不知道具體建立的是哪種披薩,只知道這是一個披薩,可以準備原料,烘烤,切片,被打包裝盒,而後送出給顧客。

定義工廠方式模式:工廠模式定義了一個建立對象的接口,不在直接使用new了,但由子類決定要實例化的類是哪個。工廠方法讓類把實例化推遲到子類了。
注:工廠方法模式可以封裝具體類型的實例化。工廠方法讓子類決定要實例化的類是哪個。所謂「決定」,並非指模式容許子類自己在運行時作決定,而是指在編寫建立者類時,不須要知道實際建立的產品是哪個。選擇了哪一個子類,天然就決定了實際建立的東西是什麼(可能有點難以理解。。。)

下圖是工廠方法模式類圖
圖片描述

好的說這麼多,下面就是具體實現的時候了,因爲此例子量稍多,可是量大不表明我會偷工減料的,嘿嘿嘿~~~

這是參考的Java工程class圖
圖片描述

代碼開始

PizzaStore(工廠類)

package Store;

import Pizza.Pizza;

/**
 * PizzaStore至關於客戶(工廠類), 日後會有不少相似此類進行拓展
 * 
 * 在這裏orderPizza()方法對pizz對象作了不少事(createPizza,prepare,bake,cut,box)
 * 可是因爲Pizza對象是抽象的(接口),
 * orderPizza()並不知道具體哪些實際類參與進來了(至關於實現Pizza接口的實現類有哪些orderPizza()方法並不知道)
 * 換句話說,這就是解耦,減小相互依賴
 * 
 * 同理createPizza()方法中也不知道建立的具體是什麼披薩 只知道披薩能夠被準備,被烘烤,被切片,被裝盒行爲而已
 * 
 */
// 披薩店
public abstract class PizzaStore {

    // 點單
    public Pizza orderPizza(String type) {
        // createPizza()方法從工廠對象中取出,
        // 而且方法也再也不這裏實現,定義爲抽象類,誰須要在拿走方法去具體實現
        Pizza pizza = createPizza(type);
        System.out.println("----製做一個" + pizza);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    // 把工廠對象移到這個方法裏,並封裝
    // 如今工廠方法爲抽象的,子類必須實現這個抽象方法
    public abstract Pizza createPizza(String item);
}

下面是「工廠」具體實現
紐約風格披薩店(分店)

package Store;

import Pizza.NYStyleCheesePizza;
import Pizza.NYStyleClamPizza;
import Pizza.NYStylePepperoniPizza;
import Pizza.NYStyleVeggiePizza;
import Pizza.Pizza;

/**
 * 此時NYStylePizzaStore所封裝的是關於如何製做紐約風格的比薩
 * 
 * @author Joy
 * 
 */

// 紐約風格披薩店(分店)
public class NYStylePizzaStore extends PizzaStore {

    // 根據披薩的不一樣,建立不一樣披薩(具體實現類)
    @Override
    public Pizza createPizza(String item) {
        if (item.equals("芝士")) {
            return new NYStyleCheesePizza();
        } else if (item.equals("素食")) {
            return new NYStyleVeggiePizza();
        } else if (item.equals("海鮮")) {
            return new NYStyleClamPizza();

        } else if (item.equals("香腸")) {
            return new NYStylePepperoniPizza();
        }
        return null;
    }
}

芝加哥披薩店(分店)

package Store;

import Pizza.ChicagoStyleCheesePizza;
import Pizza.ChicagoStyleClamPizza;
import Pizza.ChicagoStylePepperoniPizza;
import Pizza.ChicagoStyleVeggiePizza;
import Pizza.Pizza;

/**
 * 此時ChicagoStylePizzaStore所封裝的是關於如何製做芝加哥風格的比薩
 * 
 * @author Joy
 * 
 */
// 芝加哥披薩店(分店)
public class ChicagoStylePizzaStore extends PizzaStore {

    @Override
    public Pizza createPizza(String item) {
        if (item.equals("芝士")) {
            return new ChicagoStyleCheesePizza();
        } else if (item.equals("素食")) {
            return new ChicagoStyleVeggiePizza();
        } else if (item.equals("海鮮")) {
            return new ChicagoStyleClamPizza();
        } else if (item.equals("香腸")) {
            return new ChicagoStylePepperoniPizza();
        }
        return null;
    }
}

Pizza類
(至關於各個「產品」的接口)

package Pizza;

import java.util.ArrayList;

public abstract class Pizza {
    String name;// 披薩名稱
    String dough;// 麪糰類型
    String sauce;// 醬料
    ArrayList toppings = new ArrayList();// 一套佐料

    public 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.print(toppings.get(i) + "   ");
        }
    }

    public void bake() {
        System.out.println("\n" + "披薩正在烘烤,需烘烤25分鐘。。。");
    }

    public void cut() {
        System.out.println("製做完成,給披薩切片。。。");
    }

    public void box() {
        System.out.println("給披薩打包裝盒。。。");
    }

    public String getName() {
        return name;
    }

    // 輸出客人點的披薩信息
    @Override
    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((String) toppings.get(i) + "\n");
        }
        return display.toString();
    }

}

下面是各類披薩具體實現類

package Pizza;

//加州披薩店的芝士披薩
public class ChicagoStyleCheesePizza extends Pizza {
    public ChicagoStyleCheesePizza() {
        name="加州芝士披薩";
        dough="厚餅";
        sauce="番茄醬";
        toppings.add("幹碎奶酪");                
    }
    @Override
    public void cut() {    
        System.out.println("將披薩切成方形");
    }

}
package Pizza;

//加州披薩店的海鮮披薩
public class ChicagoStyleClamPizza extends Pizza {

    public ChicagoStyleClamPizza() {
        name = "加州海鮮披薩";
        dough = "厚餅";
        sauce = "番茄醬";
        toppings.add("幹碎奶酪");
        toppings.add("蟹黃");
        toppings.add("龍蝦");
        toppings.add("各類海鮮");
    }
    
    @Override
    public void cut() {    
        System.out.println("將披薩切成三角形");
    }    

}
package Pizza;

//加州披薩店裏的香腸披薩
public class ChicagoStylePepperoniPizza extends Pizza {

    public ChicagoStylePepperoniPizza() {
        name = "加州香腸披薩";
        dough = "厚餅";
        sauce = "番茄醬";

        toppings.add("幹碎奶酪");
        toppings.add("黑胡椒");
        toppings.add("菠菜");
        toppings.add("茄子");
        toppings.add("香腸切片");
    }

    @Override
    public void cut() {
        System.out.println("切成方形薄披薩");
    }

}
package Pizza;

//加州披薩店裏的素披薩
public class ChicagoStyleVeggiePizza extends Pizza {

    public ChicagoStyleVeggiePizza() {
        name = "加州素披薩";
        dough = "厚餅";
        sauce = "番茄醬";
        toppings.add("幹碎奶酪");
        toppings.add("聖女果");
        toppings.add("黑胡椒粉");
        toppings.add("紅辣椒");
        toppings.add("甜玉米粒");
        toppings.add("色拉");
    }

    @Override
    public void cut() {
        System.out.println("切成方形薄披薩");
    }
}
package Pizza;


//紐約披薩店裏的芝士披薩
public class NYStyleCheesePizza extends Pizza {

    public NYStyleCheesePizza() {
        name="紐約芝士披薩";
        dough="薄餅";
        sauce="番茄醬";
        toppings.add("幹奶酪");    
    }    
}
package Pizza;

//紐約披薩店裏的河鮮披薩
public class NYStyleClamPizza extends Pizza {

    public NYStyleClamPizza() {
        name = "紐約海鮮披薩";
        dough="薄餅";
        sauce="番茄醬";
        toppings.add("巴馬乾奶酪");
        toppings.add("蟹黃");
        toppings.add("龍蝦");
    }
}
package Pizza;




//紐約披薩店裏的香腸披薩
public class NYStylePepperoniPizza extends Pizza {

    public NYStylePepperoniPizza() {
        name = "紐約香腸披薩";
        dough="薄餅";
        sauce="番茄醬";
        toppings.add("巴馬乾酪奶酪");
        toppings.add("意大利香腸切片");
        toppings.add("大蒜");
        toppings.add("洋蔥");
        toppings.add("香菇");
        toppings.add("紅辣椒");
    }
}
package Pizza;

//紐約披薩店裏的素披薩
public class NYStyleVeggiePizza extends Pizza {

    public NYStyleVeggiePizza() {
        name = "紐約素食披薩";
        dough = "薄餅";
        sauce = "番茄醬";
        toppings.add("巴馬乾酪奶酪");
        toppings.add("洋蔥");
        toppings.add("各類蔬菜");
    }
}

測試類

package TestMain;

import Pizza.Pizza;
import Store.ChicagoStylePizzaStore;
import Store.NYStylePizzaStore;
import Store.PizzaStore;

public class TestMain {
    public static void main(String[] args) {
        PizzaStore nyStore = new NYStylePizzaStore();
        PizzaStore chicagoStore = new ChicagoStylePizzaStore();
        // System.out.println("====================================");
        // Pizza pizza1 = nyStore.orderPizza("芝士");
        // System.out.println("Joy點了一個" + pizza1.getName());
        // System.out.println("====================================");
        // Pizza pizza2 = nyStore.orderPizza("素食");
        // System.out.println("Joy點了一個" + pizza2.getName());
        System.out.println("====================================");
        Pizza pizza3 = nyStore.orderPizza("海鮮");
        System.out.println("Joy點了一個" + pizza3.getName() + " ,正在送出");
        // System.out.println("====================================");
        // Pizza pizza4 = nyStore.orderPizza("香腸");
        // System.out.println("Joy點了一個" + pizza4.getName());
        System.out.println("====================================");
        Pizza pizza11 = chicagoStore.orderPizza("芝士");
        System.out.println("Joy點了一個" + pizza11.getName() + " ,正在送出");
        // System.out.println("====================================");
        // Pizza pizza22 = chicagoStore.orderPizza("素食");
        // System.out.println("Joy點了一個" + pizza22.getName());
        // System.out.println("====================================");
        // Pizza pizza33 = chicagoStore.orderPizza("海鮮");
        // System.out.println("Joy點了一個" + pizza33.getName());
        // System.out.println("====================================");
        // Pizza pizza44 = chicagoStore.orderPizza("香腸");
        // System.out.println("Joy點了一個" + pizza44.getName());

    }
}

效果以下:
圖片描述

這個模式是個頗有用的模式,讓我更加能封裝變化了,模擬代碼已經所有放出,註釋也寫的比較清楚,如有不理解歡迎留言。

感謝你看到這裏,工廠模式上部分結束,但工廠模式內容還沒徹底結束 本人文筆隨便,如有不足或錯誤之處望給予指點,90度彎腰~~~很快我會補全完這個內容。 生命不息,編程不止!

參考書籍:《Head First 設計模式》
相關文章
相關標籤/搜索