1. 程式人生 > 實用技巧 >設計模式:工廠設計模式介紹及3種寫法(簡單工廠、工廠方法、抽象工廠)

設計模式:工廠設計模式介紹及3種寫法(簡單工廠、工廠方法、抽象工廠)


0、介紹


給一個背景:一個pizza訂購專案,pizza本身的種類要便於擴充套件和維護,那麼種類很多、製作過程也不少、還要完成訂購的功能

按照一般的思路,類圖設計如下:

其中:

  1. Pizza類按照面向物件的設計思路,製作過程對應的方法在其中實現;
  2. 由於需要不同的型別 Pizza,而在 prepare 步驟不同,所以 Pizza 做成抽象類,而兩個不同型別的 Pizza 去繼承 Pizza 類;
  3. 還需要一個的 OrderPizza 類,接受預定型別,完成對應的pizza類訂購操作;
  4. 然後提供一個入口 PizzaStore 去呼叫 OrderPizza 類,相當於客戶端。

程式碼如下:

/*
*  Pizza抽象類,讓其他的繼承
*/
public abstract class Pizza {
    protected String name;//pizza名
    //不同pizza準備不同,所以留給實現類去實現
    public abstract void prepare();
    public void bake(){
        System.out.println(name + " baking");
    }
    public void cut(){
        System.out.println(name + " cutting");
    }
    public void box(){
        System.out.println(name + " boxing");
    }
    public void setName(String name){
        this.name = name;
    }
}
/*
*  乳酪pizza
*/
public class CheesePizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("製作乳酪pizza準備材料ing");
    }
}
/*
*   希臘pizza
*/
public class GreekPizza extends Pizza {
    @Override
    public void prepare() {
        System.out.println("給希臘pizza準備原材料ing");
    }
}
/*
*  pizza訂購類
*/
public class OrderPizza {
    //獲取pizza訂單
    private String getType(){
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type");
            String type = br.readLine();
            return type;
        }catch (IOException e){
            e.printStackTrace();
            return "";
        }
    }
    //構造器
    public OrderPizza() {
        Pizza pizza = null;
        String orderType;//訂購型別
        do {
            orderType = getType();
            if (orderType.equals("greek")){
                pizza = new GreekPizza();
                pizza.setName("希臘pizza");
            }else if (orderType.equals("cheese")){
                pizza = new CheesePizza();
                pizza.setName("乳酪pizza");
            }else{
                break;
            }
            //方便除錯,把製作過程列印
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }
}
/*
*  客戶端
*/
public class PizzaStore {
    public static void main(String[] args) {
        new OrderPizza();
    }
}

這樣我們的一個 pizza 線上商店就全部完成了,似乎是一個很完美的過程呢。

優點
傳統思路,便於理解
缺點
違反了在設計模式七大原則裡面說過的 ocp 原則,即對擴充套件開放,對修改關閉

具體一些:Pizza 作為提供方,如果商店要增加一個種類,那麼除了要增加一個 Pizza 類【提供方】之外,依賴的 Order 類也要增加對於新 Pizza 的處理【使用方】(甚至擴充套件起來,還有別的依賴於 Pizza 的使用方操作類,全部都要修改),而按照 Ocp 原則,使用方不應該修改,所以我們要避免這種情況。


一、簡單工廠模式


對於上面的缺點,我們把建立 Pizza 的過程封裝到一個類裡,然後以這個類為中介進行建立,而不是再在Order裡面直接建立。這樣增加【提供方】 Pizza 的時候,修改這個類就可以了。

簡單工廠模式(靜態工廠模式)

  1. 屬於建立型模式,是工廠模式的一種,由一個工廠物件來決定創造出哪一種產品類的例項。他是工廠模式家族裡最簡單實用的模式;
  2. 簡單工廠模式定義了一個建立物件的類,由這個類來封裝例項化物件的行為;
  3. 在實際開發中,如果會用到大量建立某種物件時,就會用到工廠模式。
/*
    簡單工廠類,根據型別建立pizza物件並返回
*/
public class SimpleFactory {
    public Pizza createPizza(String orderType){
        System.out.println("使用簡單工廠模式");
        Pizza pizza = null;
        if (orderType.equals("greek")){
            pizza = new GreekPizza();
            pizza.setName("希臘披薩");
        }else if (orderType.equals("cheese")){
            pizza = new CheesePizza();
            pizza.setName("乳酪披薩");
        }else if (orderType.equals("pepper")){
            pizza = new PepperPizza();
            pizza.setName("胡椒披薩");
        }
        return pizza;
    }
}
/*
*  pizza訂購類
*/
public class OrderPizza {
    SimpleFactory factory;
    Pizza pizza = null;
    //構造器
    public OrderPizza(SimpleFactory factory) {
        setFactory(factory);
    }
    //建立過程改為呼叫工廠類
    public void setFactory(SimpleFactory factory){
        String orderType = "";
        this.factory = factory;
        do {
            orderType = getType();
            pizza = this.factory.createPizza(orderType);
            //打印製作過程
            if (pizza != null){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("訂購失敗");
                break;
            }
        }while (true);
    }
    //獲取pizza種類
    private String getType() {
            //和前面一樣
    }
}
/*
*  客戶端
*/
public class PizzaStore {
    public static void main(String[] args) {
//        new OrderPizza();
        //使用簡單那工廠模式
        new OrderPizza(new SimpleFactory());
        System.out.println("結束");
    }
}

類圖變成了這樣:

可以看到,複雜的依賴關係基本消失,工廠類 負責了所有物件的產生,因此他和使用方OrderPizza 的關係是聚合關係。

這樣一來,如果有新增的 Pizza 類,除了類的建立之外,就只用修改工廠類裡面的新建方法,而對於使用方,OrderPizza、或者即將使用 Pizza 類的其他增加進來的使用方,程式碼都不用修改,因為他們都是呼叫工廠類進行物件的建立。

(其實就是為了方便擴充套件,多抽象出來一層的思想)

補充:

簡單工廠模式之所以又叫靜態工廠模式,就是因為可以直接把工廠類的 create 方法寫成靜態,這樣的話,就不用 new 出工廠物件,而是隻需要呼叫它的靜態方法就可以。

public OrderPizzaviaStatic() {
    String orderType = "";
    do {
        orderType = getType();
        pizza = SimpleFactory.createPizza(orderType);//靜態方法
        if (pizza != null){
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }else{
            System.out.println("訂購失敗");
            break;
        }
    }while (true);
}

使用靜態與否,根據具體情況來修改


二、工廠方法模式


工廠方法模式:定義了一個建立物件的抽象方法,由子類決定要例項化的類,工廠方法模式將物件的例項化推遲到子類

思考這個問題

如果新增了一個需求,對於pizza,每次要新加地域屬性,比如現在還是3種pizza,但是有3個地方,各有這三種pizza。客戶點單的時候也會輸入地域屬性。

  • 解決方法:建立的時候利用簡單工廠模式,新建不同地域的工廠,並且每個工廠裡帶上地域屬性,其他不變,就可以實現擴充套件。
  • 但是這種方法不是最優,如果不同工廠很多的話,會需要多個工廠類。

其實把工廠類單獨作為普通類來考慮,我們能想到的改進思路就是,在他們基礎上,再提取公共介面或者抽象類。事實上,工廠方法模式的確是基於這種思路的。

使用工廠方法模式,將例項化功能抽象成抽象方法(而不是像簡單工廠模式那樣直接實現),在不同地域點餐子類中實現抽象方法

根據這個類圖很容易看得出來,其實只是對於Order部分做了一個抽象,其他的和之前的一般寫法沒有區別。

雖然沒有出現factory字樣,但是其實兩個Order子類充當了Factory的作用

  • Order 根據不同的 type 去呼叫不同的子 Order,做 create 的工作;
  • 每個 Pizza 類裡什麼也不做。

所以和上面簡單工廠的區別就是,這個根據地域分別寫了 Order 類,將 OrderPizza 作為抽象類去組織這幾個。

因此這一版程式碼,一個抽象 Pizza 類和4個子 Pizza 類完全沒有變化,剩下三個類的寫法如下:

/*
    orderPizza類,根據type呼叫不同的工廠
*/
public abstract class OrderPizza {
    //抽象方法,具體根據地域不同,讓工廠子類去實現
    abstract Pizza createPizza(String orderType);
    //構造器
    public OrderPizza(){
        Pizza pizza = null;
        String orderType;
        do {
            orderType = getType();
            pizza = createPizza(orderType);//呼叫的是抽象方法
            if (pizza != null){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else {
                System.out.println("訂購失敗");
                break;
            }
        }while (true);
    }

    private String getType() {
            //和之前一樣
    }
}
/*
    北京pizza工廠類
*/
public class BJOrderPizza extends OrderPizza{
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new BJCheesePizza();
            pizza.setName("北京乳酪pizza");
        }else if (orderType.equals("pepper")){
            pizza = new BJPepperPizza();
            pizza.setName("北京胡椒pizza");
        }
        return pizza;
    }
}
/*
    上海pizza工廠類
*/
public class SHOrderPizza extends OrderPizza {
    @Override
    Pizza createPizza(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new SHCheesePizza();
            pizza.setName("上海乳酪pizza");
        }else if (orderType.equals("pepper")){
            pizza = new SHPepperPizza();
            pizza.setName("上海胡椒pizza");
        }
        return pizza;
    }
}

最後,我們還是用一個 PizzaStore 類模擬一下客戶端,接受不同的輸入,來呼叫不同的 Order 類就可以。


三、抽象工廠模式


抽象工廠模式:

  1. 定義一個 interface 用於建立相關或有依賴關係的物件簇,而無需指明具體的類
  2. 將簡單工廠模式和工廠方法模式進行整合,其實相當於將簡單工廠模式再網上抽象一層
  3. 包含AbstractFactory(抽象工廠)和具體實現工廠子類,根據需要的型別使用對應的子類,這樣簡單工廠類變成了工廠簇,更有助於程式碼維護

這種實現方式的類圖是這樣的:

相比上面的工廠方法模式,抽象模式抽象出來一個Factory介面,兩個實現類分別作為具體的工廠去實現介面的方法,然後聚合到Order類上,整體思路和上一種是一樣的。

程式碼層面:

所有Pizza的內容都一樣,BJFactory 和 SHFactory 和上面所說的,用工廠方法模式寫的程式碼也一樣,不過改成實現介面 AbstractFactory,而不是實現order抽象類。

AbstractFactory:

/*
    抽象工廠模式的抽象層:介面
*/
public interface AbstractFactory {
    //讓下面子類實現具體
    public Pizza createPizza(String orderType);
}

OrderPizza 類和第二種模式的 OrderPizza 類基本是一樣的;
區別就在於,不是根據傳入的 Pizza 的 type 直接 create,而是去呼叫 Factory 實現類,所以是和第一種模式程式碼一樣。

/*
    聚合的order類,和工廠介面互動
*/
public class OrderPizza {
    AbstractFactory factory;
    //構造器
    public OrderPizza(AbstractFactory factory){
        setFactory(factory);
    }
    //根據不同實現類進行構造
    private void setFactory(AbstractFactory factory){
        Pizza pizza = null;
        String orderType = "";
        this.factory = factory;
        do {
            orderType = getType();
            //factory是接受的不同factory,取決於不同實現類
            pizza = factory.createPizza(orderType);
            if (pizza != null){
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("訂購失敗");
                break;
            }
        }while (true);
    }

    private String getType() {
    }
}

最後,我們寫上 Store類就可以,根據輸入的不同,new出 Order 類,並且傳入不同地域的工廠引數。

總結一下可以發現,這三種工廠模式,大同小異,就是對於簡單工廠模式的不同程度的抽象。


四、工廠模式在JDK的應用


jdk 的 Canlendar類中,使用的就是 簡單工廠模式

當我們寫下這行程式碼:

Calendar calendar = Calendar.getInstance();//靜態方法

的時候,getInstance 方法去呼叫了

createCalendar 方法,返回一個 Canlendar 例項。

而在建立的方法裡面如下:

做一系列判斷,看裡面獲取的各種引數,相當於這個工廠根據不同的情況去決定選擇哪一種(具體在這個類裡是時區、地理位置之類的),最後將建立好的 calendar 返回。

也就是對應我們說的工廠模式第一種,簡單工廠模式。


五、總結


工廠模式的意義

將例項化物件的程式碼提取出來,放到一個類裡面統一管理和維護,達到各種依賴關係的解耦,從而提高專案的可擴充套件性和可維護性。(最後是達到設計模式的 ocp 原則)

工廠模式分為三種:

  1. 簡單工廠模式:如果要建立一種物件的行為比較多,將所有的工作放到一個工廠類裡,每次呼叫這個類的方法就可以。
  2. 工廠方法模式:如果建立的物件種類要增加,那就分類多寫幾個工廠類,讓他們實現一個抽象方法,這樣能夠做了區分後呼叫不同的工廠類。
  3. 抽象工廠模式:再加一層抽象工廠層,那麼操作層面就只和固定的抽象工廠類互動,不用管有幾種工廠實現類。

工廠模式體現的編碼技巧:

  • 建立物件例項的時候不要直接 new 類,而是要把這個動作交給一個工廠的方法裡,並且返回。
  • 不要讓類直接繼承具體的類,而是繼承抽象類或者實現介面。
  • 不要覆蓋基類中已經實現的方法。