設計模式,創建型模式之工廠模式

1 概述

創建型模式,提供了一種創建對象的最佳實踐。工廠方法模式的核心思想,是通過統一的工廠類來獲取對象,而不需要調用類的構造方法。

2 優點

  1. 可以將類的實例化過程延緩到子類。調用者無需知道接口/抽象類的具體實現是什麼,利用工廠方法即可獲取類的實例,降低與調用者的耦合度。
  2. 隱藏類的構造細節,降低類創建的複雜度,提高程序可讀性。
  3. 可以根據不同環境/參數,從工廠構造不同的方法。

3 案例

有一個飯店的接口,飯店裏有廚師和服務員。我們定義KFCPizzaHut兩個飯店:

interface Restaurant {
    void getCook();
    void getWaiter();
}

public class KFC implements Restaurant {

    @Override
    public void getCook() {
        System.out.println("I'm KFC cook.");
    }

    @Override
    public void getWaiter() {
        System.out.println("I'm KFC waiter.");
    }
}

class PizzaHut implements Restaurant {

    @Override
    public void getCook() {
        System.out.println("I'm PizzaHut cook.");
    }

    @Override
    public void getWaiter() {
        System.out.println("I'm PizzaHut waiter.");
    }
}

3.1 工廠類型1

再定義一個工廠方法RestaurantFactory,從工廠中,很容易就能根據類型獲取對應的飯店:

public class Test {
    public static void main(String[] args) {
        RestaurantFactory factory = new RestaurantFactory();
        Restaurant kfc = factory.createRestaurant(RestaurantFactory.RestaurantType.KFC);
        Restaurant pizzaHut = factory.createRestaurant(RestaurantFactory.RestaurantType.PizzaHut);
        kfc.getCook();
        pizzaHut.getWaiter();
    }
}

class RestaurantFactory {
    enum RestaurantType {
        KFC, PizzaHut
    }

    Restaurant createRestaurant(RestaurantType type) {
        switch (type) {
            case KFC: return new KFC();
            case PizzaHut: return new PizzaHut();
            default: System.out.format("Invalid restaurant %s", type); return null;
        }
    }
}

輸出:

I'm KFC cook.
I'm PizzaHut waiter.

比如Spring中的BeanFactory使用的就是這種模式:getBean方法用Bean類型/名字作爲參數,返回對應的Bean
JDK中的Calendar類,也是用的這種模式:

public static Calendar getInstance(TimeZone zone, Locale aLocale) {
    ...
    // 根據參數創建不同的Calendar實例
    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
            case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
            }
        }
    }
    ...
}

如果實例類型相對固定,那麼上述模式能很好的滿足需求。但是如果實例類型不確定,那麼每當需要新增類型的時候,都需要改動原先的方法,對開閉原則遵循得不好。於是有了第二種類型。

3.2 工廠類型2

依然是飯店的例子,我們通過如下方式定義工廠:

public class Test {
    public static void main(String[] args) {
        KFCFactory kfcFactory = new KFCFactory();
        PizzaHutFactory pizzaHutFactory = new PizzaHutFactory();
        Restaurant kfc = kfcFactory.createRestaurant();
        Restaurant pizzaHut = pizzaHutFactory.createRestaurant();
        kfc.getCook();
        pizzaHut.getWaiter();
    }
}

class KFCFactory {
    Restaurant createRestaurant() {
        return new KFC();
    }
}

class PizzaHutFactory {
    Restaurant createRestaurant() {
        return new PizzaHut();
    }
}

輸出:

I'm KFC cook.
I'm PizzaHut waiter.

上述方式,每新增一個類別,只需要新增一個對應的工廠即可,原先的工廠方法以及實例無需做任何修改。如LoggerFactory就是這種類型的工廠模式:

public static Logger getLogger(String name) {
    // getLogger最終是委託給ILoggerFactory去做的。
    // 新增Logger獲取方式,只需新增ILoggerFactory的實現類,擴展性很強。
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

但是這種方式,對每一種類別,都要有一個工廠類,複雜度相對較高。實際中,還是第一種類型使用比較頻繁。

4 總結

工廠方法模式是使用很廣泛的一種創建型模式,幾乎能在所有的開源框架中見到。很典型的特點就是,工廠類以Factory字樣結尾:-)。

文中例子的github地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章