官术网_书友最值得收藏!

4.1 說明

Define an interface for creating an object,but let subclasses decide which class to instantiate.Factory Method lets a class defer instantiation to subclasses.

——Design Patterns : Elements of Reusable Object-Oriented Software

工廠類型要解決的是怎么new()的問題。

讀者可能覺得這有什么關系呢?我需要用某個對象直接new()出來就可以了,邏輯上需要幾個蘋果,我就new()幾個蘋果。例如:“Apple apple = new Apple()”。

但事情遠沒有那么簡單,當你把代碼提交后,沒幾天測試部門告訴你編譯時出現錯誤。奇怪?我什么都沒做,這不是“人在家中坐,麻煩天上來”嗎?過去一看,原來是開發Apple 類的同事為了讓對象看上去更有質感,增加了錐光效果,構造函數簽名有點變化。其實需要你做的事情不多,就是略微修改后重新編譯。但麻煩才剛剛開始,以后各種麻煩會不斷出現——大家覺的蘋果太單調,增加了櫻桃類(Cherry)、芒果類(Mango)……雖然每件事都不大,就是多new()幾次,多編譯幾次,但麻煩總是不斷,別人代碼動一動你也要跟著一起動。

小事情帶來大麻煩,為什么?

問題的癥結在于我們對對象的創建過程沒有控制,因此我們就從這里入手,看看怎么解決。本章的工廠方法模式和后續幾章的創建型模式,就要集中解決這類問題。

筆者一直認為工廠方法模式是GOF 23個模式中最具啟發效果的,它告訴我們可以通過增加新的對象專門管理“變化”。例如,我們為了解決new()引起的“變化”,就引入了工廠類型,由新增的工廠類型專門處理new()相關的“變化”,確??蛻舫绦虿皇苓@些變化的直接影響。

4.2 簡單工廠

4.2.1 最簡單的工廠類

作為后面要介紹的工廠方法模式和抽象工廠模式的 “預備”內容,我們首先用最樸實的方式完成一個工廠,通過它分析工廠與抽象類型間的構造關系,其代碼如下:

Java
interface Product {}
class ProductA implements Product{}
class ProductB implements Product{}
Unit Test
public class SimpleFactoryFixture {

class Factory{ /** * 由工廠類決定到底實例化哪個子類 * @return 符合客戶程序要求的子類實例 */ public Product newInstance(){ return new ProductA(); } }

@Test public void testSimpleFactory(){ assertTrue(new Factory().newInstance() instanceof ProductA); } }

采用工廠類型與直接使用new()有什么不同?

●我們對待加工的類型做了抽象(Interface Product)。

●定義了專門負責構造的類型——Factory。

●通過Product接口隔離了客戶程序與具體類型(ProductX)的依賴關系,在客戶程序視野內根本就不存在ProductX,只有Product接口,而Product接口相對于各個具體的ProductX而言更加穩定,不容易出現“變化”。

●即使ProductX增加、刪除方法或屬性,也無礙大局,只要按照要求實現Product接口就可以,客戶程序無須關心ProductX的變化。Factory將ProductX的變化與客戶程序隔開了。如果站在工程的角度,上面的示例還有些不盡如人意的地方:

●對于開發人員而言,較之直接實例化new ProductX()要多寫一個工廠類型(Factory),當ProductX變化的時候,Factory類型需要反復編譯,如果沒有其他控制措施,客戶程序也“閑不下來”,它也要經?!芭阃本幾g。

●好的需求分析師可以在實施之前分析清楚85%的需求,好的架構師在把這些需求轉換為實際技術框架的時候大概能做到忠于90%的需求,作為開發人員,設計的時候能夠詳細處理95%的內容就很不錯了——100% - 85%×90%×95% = 27.3%,也就是說,即便你身在一個精英團隊,也可能有1/4的內容到了編碼的時候還無法得到準確分析,ProductX的變化在所難免。

此外,還有一個效率問題。如果構造一個Factory實例,并用它獲取一個抽象類型實例后就不再使用,資源上有些浪費,尤其當這個過程非常頻繁的時候。項目中可以通過如下幾種方式解決:

●把工廠實例作為參數注入到操作中,而不是在每個方法內部自行創建,如下面的ProductConsumer類就定義為通過構造注入方式獲得工廠類型實例:

Java
class ProductConsumer{

Factory factory;
/** * 從外部注入的引用,而不是每次操作時都要獲取一個新的Factory類型實例 * @param factory 工廠類型實例 */ public ProductConsumer(Factory factory){this.factory = factory;}
public void handleWithProductMethod1(){ Product product = factory.newInstance(); // 其他需要依賴于Product的操作 }
public void handleWithProductMethod2(){ Product product = factory.newInstance(); // 其他需要依賴于Product的操作 } }

●把工廠設計為單件(Singleton)方式,因為工廠的職責相對單一,所有客戶程序共享唯一的一個Factory類型實例。

●使用靜態類,構造靜態工廠,將工廠所有負責創建類型實例的方法都聲明為Static。雖然在經典的設計模式書籍示例中并沒有采用,但它未嘗不是一個有效節省資源的途徑,而且它給實例化各種抽象對象的方法起了一個名字,客戶程序可以通過這些命名上更富含義的方法獲得各種抽象對象。

不過切記:靜態類不能被繼承,所以看起來靜態類更像以前的API集合,有點不那么“面向對象”的味道。其示例代碼如下:

Java 簡單靜態工廠
class StaticFactory{

public enum Category{ A, B }
private static final Category DEFAULT_CATEGORY = Category.A;

public static Product newInstance(Category category){ switch(category){ case A: return new ProductA(); case B: return new ProductB(); default: throw new UnsupportedOperationException (); } }
public static Product newInstance(){ return newInstance(DEFAULT_CATEGORY); } }
Unit Test
@Test
public void testStaticFactory()throws ClassNotFoundException{
        assertTrue(StaticFactory.newInstance() instanceof ProductA);
        assertTrue(StaticFactory.newInstance(Category.A) instanceof
ProductA);
        assertTrue(StaticFactory.newInstance(Category.B) instanceof
ProductB);
}

靜態工廠的靜態結構如圖4-1所示。

圖4-1 靜態工廠的靜態結構

4.2.2 簡單工廠的局限性

上例的簡單工廠(和簡單靜態工廠)大體解決了外部new()的問題,它把目標實例的創建工作交給一個外部的工廠完成,是設計思想模式化的一個很不錯的引子。但如果應用中需要工廠的類型非常多,各個工廠類型的職責又雷同——就是一個new()的替代品,此時用我們面向對象的“嗅覺”不難發現這時候就需要進一步抽象了,于是出現了新的發展:工廠方法模式和抽象工廠模式。

4.3 經典回顧

作為簡單工廠的一個擴展,工廠方法模式的意圖就是把類的實例化過程延遲到子類,將new()的工作交給工廠完成。同時,對工廠自身進行抽象,然后客戶程序基于工廠的抽象行為構造所需的類型實例。

它適合如下情景:

●客戶程序需要隔離自己與待new()類型間的耦合關系。

●開發階段客戶程序還無法明確預知需要new()的具體類型。

●客戶程序將new()的工作交給外部對象完成。

●各種需要new()的對象雖然可以統一抽象為某個接口,但它們的繼承關系太過煩瑣,層次也比較復雜,這時同樣可以通過工廠方法解決這個問題。工廠方法模式主要有4個角色。

●抽象產品類型(Product):對待構造類型抽象后的接口。

●具體產品類型(ConcreteProduct):具體待構造類型,需要由工廠延遲實例化。

●工廠類型的抽象接口(Factory)。

●具體工廠類型(ConcreteFactory)。

經典的工廠方法模式的靜態結構如圖4-2所示。

圖4-2 經典的工廠方法模式的靜態結構

示例代碼如下:

Java
/**抽象的工廠類型描述*/
public interface Factory {
   /**
    * 工廠類型的職責——構造實例
    * @return 客戶程序所需的實例
    */
    Product newInstance();
}
Java 兩個具體工廠類型
class FactoryA implements Factory{
    @Override
    public Product newInstance() {
        return new ProductA();
    }
}

class FactoryB implements Factory{ @Override public Product newInstance() { return new ProductB(); } }
Java Unit Test
@Test
public void testClassicFactoryMethod(){
    Factory factory = new FactoryA();
    assertTrue(factory.newInstance() instanceof ProductA);
    factory = new FactoryB();
    assertTrue(factory.newInstance() instanceof ProductB);
}

通過這個例子可以看到,當客戶程序作為Product消費者的時候可以把頻繁變化的某個ProductX隔離在自己的視線之外,自身不會受到它的影響。

4.4 解耦工廠類型與客戶程序

雖然經典工廠方法模式通過Product、ConcreteProduct、Factory和ConcreteFactory這4個角色解決了客戶程序對Product的獲取問題,但沒有介紹如何把Factory放到客戶程序中,而且大部分介紹設計模式的書籍會給出大致如下示例代碼:

Java
class Client{
    public void someMethod(){
        //獲得了抽象Factory的同時與FactoryA產生依賴
        Factory factory = new FactoryA();
        Product product = factory.newInstance();
        // 后續操作僅依賴抽象的Factory和Product
        …
    }
}

這就產生問題了,如果讓客戶程序直接來new()某個Concrete Factory,由于Concrete Factory依賴于Concrete Product,因此還會形成客戶程序對具體產品類型的間接依賴關系,背離了這個模式的初始意圖。

采用前面提到的“依賴注入”辦法,把客戶程序需要的Factory給它注入進去。該方式的靜態結構要在經典工廠方法模式上做一些修改,如圖4-3所示。

圖4-3 增加了裝配對象的工廠方法靜態結構

實際工程中很多時候不得不做這項工作——將Concrete Product與客戶程序分離,其原因是我們不想因上游開發人員微小的修改就迫使我們重新檢查代碼,編譯、重新單元測試后再記錄,這種活動在項目開發的中、后期常常是非常令人厭惡的。

除了采用依賴注入的方式外,我們可以“遞歸套用”工廠模式的思路,先設計一個構造Factory接口的工廠,然后所有客戶程序從這個“構造工廠的工廠”中獲得滿足Factory接口要求的某個工廠類型實例,然后再通過它獲得所要的Product產品。

實際項目中,由于“構造工廠的工廠”的職責非常單一,而且面向系統全局,因此經常會將它設計為靜態工廠的形式。

Java 負責構造某類產品的工廠類型的抽象定義
public interface Factory<T>{
    T newInstance();
}
Java 構造工廠的工廠類型
/**
 * 創建工廠的工廠
 * 此處定義為靜態形式的工廠
*/
public class Factories{
    /*** 登記待構造產品類型及相應工廠類型*/
    private static final Map<Class<?>,Class<?>> registry……;

/*** 封閉 Factories 類型的構造函數*/ private Factories(){}
/** * 配置待構造產品類型及相應工廠類型 * @param productType 待構造產品類型 * @param factoryType 相應工廠類型 */ public static void config( Class<?> productType,Class<?> factoryType){ registry.put(productType,factoryType); }
/** * 構造實際負責生產某類產品的工廠類型實例 * @param <T> 工廠負責構造的抽象產品類型 * @param productType 抽象的產品類型 * 這個參數本可以省略 * 但因為Java泛型會在運行過程中執行類型擦除 * 所以需要增加該參數 * @return 構造實際負責生產某類產品的工廠類型實例 */ @SuppressWarnings("unchecked") public static <T> Factory<T> newFactory( Class<?> productType) throws ClassNotFoundException, InstantiationException, IllegalAccessException{
if(!registry.containsKey(productType)) throw new ClassNotFoundException(); return (Factory<T>) (registry.get(productType).newInstance()); } }
Java 定義需要加工各類產品類型
public interface X {}
public class X1 implements X{}
public class X2 implements X{}
public class X3 implements X{}
public interface Y {}
public class Y1 implements Y{}
public class Y2 implements Y{}
Java 定義加工各類產品的工廠類型
public class FactoryXA implements Factory<X>{
    @Override
    public X newInstance() {
        return new X1();
    }
}

public class FactoryXB extends FactoryXA{}
public class FactoryYA implements Factory<Y>{ @Override public Y newInstance() { return new Y1(); } }
Unit Test
public class FactoryOfFactoryFixture {
    @BeforeClass
    public static void setUpClass(){
        //登記抽象類型和工廠類型的對應關系
        Factories.config(X.class,FactoryXA.class);
        Factories.config(Y.class,FactoryYA.class);
    }

@Test public void testNewInstanceViaFactoryOfFactory() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Factory<X> factoryOfProductX = Factories.newFactory(X.class); assertTrue(factoryOfProductX.newInstance() instanceof X); assertTrue(factoryOfProductX.newInstance() instanceof X1);
Factory<Y> factoryOfProductY = Factories.newFactory(Y.class); assertTrue(factoryOfProductY.newInstance() instanceof Y); assertTrue(factoryOfProductY.newInstance() instanceof Y1); } }

上例中,客戶程序并不用自己主動尋找具體工廠類型,因此把自己和具體的工廠類型劃清了界限。其實這個思路很簡單,就是當我們發現工廠類型本身(如FactoryXA、FactoryXB、FactoryYA)因為與“具體實現”產生依賴,進而導致客戶程序與“具體實現”產生依賴的時候,就再用另一個工廠(如這里的Factories類)來構造工廠,延續這個思路就形成了后面要介紹的抽象工廠模式。

需要注意的是,因為類型擦除,所以newFactory()方法定義時需要同時傳遞泛型參數和抽象產品類型,兩者其實基本應該是一個類型,但因為運行時類型參數被擦除了,所以不能通過T.class直接查找所需的工廠類型。當然,同樣因為類型擦除,newFactory()方法可以定義非泛型類型的Factory返回結果,但這等于放棄本可以嚴謹定義的方法簽名。

4.5 基于配置文件的工廠

4.5.1 基于配置文件解耦工廠接口和具體工廠類型

上面的方法雖然已經解決了很多問題,但工程中我們往往會要求Factories類知道更多的Factory/Concrete Factory配置關系,而且為了不用反復引用并編譯代碼,作為開發人員我們希望可以把這些涉及運行維護的工作交到系統管理員或配置人員手中,一個常見的選擇就是把需要加載的Factory/Concrete Factory對應關系列表保存到配置文件中。

這種情況其實在生活中經常遇到:假設你是班里的活躍分子,畢業幾年后同學們想找到其他人時總會給你發個消息,A找B要找你,D找F也找你。對你而言如果要找的人有手機還好辦,有些人只是IM工具上有個賬號,而大家平時用的IM工具又總是“土洋結合”,每個人偏好不同,所以就麻煩了,可能經常要找不同的人索要聯系方式。不妨變通一下——在校友錄上留個帖子,大家誰變動都自己在里面登記,就好像我們用配置文件一樣,一段時間后如果A再要G的聯系方式,你只要根據帖子給他一個聯系方式即可,而不用和G直接打交道。

下面我們將上例中需要硬編碼調用Factories.config()方法配置的Factory/Concrete Factory信息登記在配置文件中,看一下是否能讓我們設計的工廠更加易于維護:

marvellousworks.practicalpattern.xml
<?xml version="1.0" encoding="UTF-8"?>
<practicalpatterns>

<configSections> <add name="factorymethod" type="…….FactoryMethodConfigSection"/> </configSections>
<!--工廠方法模式一章使用的配置節--> <factorymethod> <!--映射關系的配置元素集合--> <factories> <!--登記抽象類型和構造其工廠類型映射關系的配置元素--> <add producttype="…….X" factorytype="…….FactoryXA"/> <add producttype="…….Y" factorytype="…….FactoryYA"/> </factories> </factorymethod>
</practicalpatterns>
Unit Test
/***通知Factories讀取配置文件并更新產品類型與工廠類型間的映射關系*/
@BeforeClass
public static void setUpClass()
    throws
        XPathExpressionException,
        DOMException,
        ClassNotFoundException{
    Factories.refreshConfig();
}

@Test public void testNewInstanceViaFactoryOfFactory() throws ClassNotFoundException, InstantiationException, IllegalAccessException{ Factory<X> factoryOfProductX = Factories.newFactory(X.class); assertTrue(factoryOfProductX.newInstance() instanceof X); assertTrue(factoryOfProductX.newInstance() instanceof X1);
Factory<Y> factoryOfProductY = Factories.newFactory(Y.class); assertTrue(factoryOfProductY.newInstance() instanceof Y); assertTrue(factoryOfProductY.newInstance() instanceof Y1); }

通過這個改造,即便在系統上線后,如果需要修改Factory的具體類型,一樣可以通過修改配置文件的方式將新開發的工廠類型注冊進去。這樣,客戶程序成為一個基于配置定義的可以動態加載的框架。

不僅如此,Factories的開發人員也不用經常編譯、重新提交代碼了,開發人員只要寫好一個比較穩定的框架,后面由其他人具體實現Factory類型,至于部署上線,那是下游開發人員的事情,相互間集成的邊界很明確。

4.5.2 基于配置文件解耦工廠類型和具體工作產品

上面只是讓客戶程序脫離了具體工廠類型,但使用本模式的關鍵目的是構造Product而不是Factory,如果一味依據該模式的經典方式定義FactoryXA構造X、FactoryYA構造Y,這就需要反復創建很多工廠類型,實際項目中我們更希望能通過傳遞參數的方法讓工廠類型更具普適性。

同樣是這個問題,如果我們站在客戶角度看上面的示例,也有一點遺憾——客戶程序在運行過程中自主性選擇太少,對于每類產品(無論是X還是Y),只能配置一個工廠,也就是說,只能產生唯一不變的一個具體產品實例(X1/X2/X3或Y1/Y2)。

為此,我們還需更進一步的擴展,為客戶程序提供更多的運行態支持。擴展從哪里入手好呢?

●調整客戶程序?我們要做的就是隔絕客戶程序與具體new()的對應關系,如果要調整客戶程序才能實現還不如不做。

●擴充配置項?這可以,它對于客戶程序而言是透明的。

●擴展Factories類型?這樣做實需要一些微調,以增加客戶程序自主性,使其能夠指定生產特定產品(X或Y)工廠的參數。

●調整Concrete Factory(FactoryXA、FactoryXB、FactoryYA)?它們應該就不需要調整了。

下面我們繼續擴展上面的示例,選擇Factories和配置文件作為切入點,為了不破壞現有的客戶程序,我們在擴展時要保留對原有接口的兼容。

怎么能既保證客戶程序可以靈活選擇工廠類型,允許其傳遞參數,同時又要兼容以前的接口呢?那就是重載,然后對于每類產品定義一個“默認的”工廠類型。

下面我們綜合上面的思路,形成如下示例:

Java 調整后的Factories
/**
* 創建工廠的工廠
* 此處定義為靜態形式的工廠
*/
public class Factories{

/**登記待構造產品類型及相應工廠類型*/ private static Map<SimpleEntry<Class<?>,String>,Class<?>> registry …;
/**封閉 Factories 類型的構造函數*/ private Factories(){}
/**兼容原有的配置接口*/ public static void config(Class<?> productType, Class<? extends Factory<?>> factoryType)…
/**新增的配置接口*/ public static void config(Class<?> productType, String name,Class<? extends Factory<?>> factoryType)…
/**從配置文件直接加載配置信息*/ public static void refreshConfig()…
/**返回負責生產某類產品的工廠類型實例*/ @SuppressWarnings("unchecked") public static <T> Factory<T> newFactory( Class<?> productType,String name)…
/**兼容原有的構造工廠類型實例接口*/ public static <T> Factory<T> newFactory( Class<?> productType)… return newFactory(productType,DEFAULT_NAME); } }
marvellousworks.practicalpattern.xml
<?xml version="1.0" encoding="UTF-8"?>
<practicalpatterns>

<configSections> <add name="factorymethod" type="…….FactoryMethodConfigSection"/> </configSections>
<!--工廠方法模式一章使用的配置節--> <factorymethod> <!--映射關系的配置元素集合--> <factories> <!--登記抽象類型和構造其工廠類型映射關系的配置元素--> <!-- 'default'屬性表示是否為該類產品默認映射的工廠類型 --> <add name="a" producttype="…….X" factorytype="…….FactoryXA" default="true"/> <add name="b" producttype="…….X" factorytype="…….FactoryXB"/> <add name="x" producttype="…….X" factorytype="…….FactoryXB" default="false"/> <add name="c" producttype="…….Y" factorytype="…….FactoryYA" default="true"/> </factories> </factorymethod> </practicalpatterns>
Unit Test
public class FactoryWithConfigurationFixture {

/*** * 通知Factories讀取配置文件并更新產品類型與工廠類型間的映射關系 */ @BeforeClass public static void setUpClass() throws XPathExpressionException, DOMException, ClassNotFoundException{ Factories.refreshConfig(); }
@Test public void testNewInstanceViaFactoryOfFactoryByName() throws ClassNotFoundException, InstantiationException, IllegalAccessException{
Factory<X> factoryOfProductX = Factories.newFactory(X.class,"a"); assertTrue(factoryOfProductX.newInstance() instanceof X); assertTrue(factoryOfProductX.newInstance() instanceof X1); } }

由于查找工廠和產品類型的映射關系需要兩項數據——名稱、產品類型,因此示例采用Java SE內置的java.util.AbstractMap.SimpleEntry<Class<?>,String> (<類型,名稱>)作為鍵值。

示例驗證工廠類型可以按照邏輯名稱加工指定類型的產品,這點對于程序沒有太大的意義,但它對于系統的設計人員和運維人員很有意義,因為我們已經習慣于用自己熟悉的“邏輯”名稱指代和管理各類資源,需求分析時也是,我們一般都習慣于“財務庫”、“人員庫”之類的邏輯名稱,至于它們具體是Oracle、SQL Server,還是Database名稱先暫且不管。我們管理信息系統同樣也喜歡用邏輯名稱稱呼,好說也好記。

所以,這個改造雖然簡單,卻把握住了需求、設計、實現、運行和維護(以下簡稱運維)主線,是面向人而不是面向機器管理信息系統的考慮。

4.6 批量工廠

此外,實際項目中經常需要加工一批對象,如果按部就班地采用一件接一件的模式來生成,效率上相對較低,最好專門設計獨立的批量工廠。其實,“工廠”這個名稱體現的也是這個思想,現實中除了造衛星、航空母艦之類的工廠是單件生產外,大多數工廠都是批量生產產品的,如果有人騎自行車批發啤酒的時候,要等24次newInstance()才能湊足一件,這也太累了。

本著OCP原則,我們定義一個新的接口,在Factory<T>接口的基礎上增加批量指示,這樣擴展后的接口定義如下:

Java 具有“批發”功能的工廠類型抽象定義
public interface BatchFactory<T> extends Factory<T> {
    /**
     * 構造產品實例
     * @param quantity 待構造實例數量
     * @return 一批目標實例
     */
    Collection<T> newInstance(int quantity);
}

4.7 典型工程化實現

上述兩個基于配置的實現看上去已經解決很多問題,現在再回顧一下其中的經驗:

●從客戶程序的角度看,工廠方法的方法簽名已經很一致、很簡潔。

●客戶程序與工廠接口(Factory)、客戶程序與產品接口(Product)、工廠接口(Factory)與具體工廠(FactoryX)類型、產品接口與具體產品類型(ProductX)這4個依賴關系從代碼中剝離,變成完全基于外部配置文件的依賴。

●新的產品類型和工廠類型即便在系統上線后仍可以通過修改配置文件的方式不斷補充。

●對于那些需要長期運維的項目,尤其是自己開發、自己運維的項目而言,它更適于部署和更新。

但同時它也有不足:

最明顯的就是“多頭管理”問題,也就是要為每“類”抽象產品定制特別的工廠接口并實現之。

其次,某些場合下使用配置文件定義產品類型接口與工廠接口的工作,不用興師動眾地使用配置文件,換言之有時候還要提供運行時動態配置的功能。

為此,我們能否把上面兩節完成的工廠類型進一步整理,設計一個更為通用的工廠類型,由它全權承擔項目中工廠的典型職責,盡量避免如上例中反復構建工廠類型(FactoryXA、FactoryXB、FactoryYA)的工作。

綜合本節的分析,權衡各類功能性要求后,我們設計了一個更符合生產環境要求的工廠類型,如圖4-4所示。

圖4-4 更符合生產環境要求的工廠類型

Java 更適合生產環境的工廠類型抽象定義
/**
 * 一個更符合生產環境要求的通用工廠類型
 *
 * 因為Java 5/6/7泛型運行態的類型擦除,無法直接 new T()或者訪問T.class
 * 所以在相關方法需要重復定義類型參數<T>和Class<?> abstractClass參數
 * 使用中,兩個參數一般應保持一致
*/
public interface Factory{
    /**
     * 構造目標實例
     * @param <T> 目標產品類型,一般采用抽象的產品類型
     * @param abstractClass 抽象產品類型
     * @return 目標實例
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    <T> T newInstance(Class<?> abstractClass)
        throws
            InstantiationException,
            IllegalAccessException;
    /**
     * 構造目標實例
     * @param <T> 目標產品類型,一般采用抽象的產品類型
     * @param abstractClass 抽象產品類型
     * @param name 邏輯名稱
     * @return 目標實例
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    <T> T newInstance(Class<?> abstractClass,String name)
        throws
            InstantiationException,
            IllegalAccessException;

/** * 配置抽象產品類型與具體產品類型的映射關系 * 項目中,也可以通過這些重載的接口,注入從配置文件提取的配置信息 * @param abstractClass 抽象的產品類型 * @param implClass 具體產品類型 * @param name 指定目標產品的邏輯名稱 * @return 工廠類型自身,定義上采用連貫接口的方式 */ Factory config(Class<?> abstractClass,Class<?> implClass,String name);
Factory config(Class<?> abstractClass,Class<?> implClass); Factory config(Map<SimpleEntry<Class<?>,String>,Class<?>> configSettings); }
Java Unit Test
public class FactoryFixture {

Factory factory;
@Before public void setUp(){ factory = new FactoryImpl() .config(X.class,X1.class) // default .config(X.class,X1.class,"1") .config(X.class,X2.class,"2") .config(X.class,X3.class,"3") .config(Y.class,Y1.class) // default .config(Y.class,Y1.class,"1") .config(Y.class,Y2.class,"2"); } @Test public void testNewInstance() throws InstantiationException, IllegalAccessException{ assertTrue(factory.newInstance(X.class) instanceof X); assertTrue(factory.newInstance(X.class) instanceof X1); assertTrue(factory.newInstance(X.class,"1") instanceof X1); assertTrue(factory.newInstance(X.class,"2") instanceof X2); assertTrue(factory.newInstance(X.class,"3") instanceof X3);
assertTrue(factory.newInstance(Y.class) instanceof Y); assertTrue(factory.newInstance(Y.class) instanceof Y1); assertTrue(factory.newInstance(Y.class,"1") instanceof Y1); assertTrue(factory.newInstance(Y.class,"2") instanceof Y2); } }

上面的示例表明,新的工廠類型不僅可以完成經典工廠方法模式所希望實現的各項要求,同時它可以作為整個項目中一個獨立的而且是唯一的工廠入口,供項目中各子系統訪問和使用。原因在于它的底層將工廠接口與抽象產品類型的依賴關系變成基于JDK“萬能工廠”類型java.lang.Class基于參數類型的構造過程。此外,具體項目中可能還要進一步修改這個工廠類型,提供更多newInstance()方法的重載,如具有構造參數的newInstance()方法或者具有批量構造指示的newInstance()方法。如果為了便于部署和后續運維需要,還可以參考前面的介紹,借助config()方法將配置文件中定義的類型映射關系加載到新的具體工廠類型中。

4.8 小結

工廠方法模式重新詮釋了如何去new()的問題,通過引入新的對象,在很大程度上解放了客戶程序對具體類型的依賴,其方法就是延遲構造到子類,但依賴關系是始終存在的,解放一個對象的時候等于把麻煩轉嫁到另一個對象上。為此,項目中往往最后把推不掉的依賴關系推到配置文件或“萬能工廠”——java.lang.Class上,也就是推給了JDK環境,這樣經典的工廠方法模式往往在項目中被演繹成“工廠方法 + 依賴注入 + 配置文件訪問”的組合方式。

考慮到性能因素,或者干脆為了省去多次調用的麻煩,項目中常要求生成一批對象實例,這時候就會用到批量工廠方法。

而且,如前面章節所討論的,寫Demo和完成一個項目是兩碼事,其中一個關鍵的因素就是人,為了讓整個實施過程中工廠的實現“中規中矩”,有時候要統一一些內容,泛型工廠能從根本上約束整個工廠體系中工廠方法的命名。

不過,本章也留下兩個需要進一步完善的問題,其在實際項目中常常遇到,將在“自我檢驗”的參考答案和后續章節中介紹:

●處理帶各種參數的構造函數。

●對象的構造過程本身涉及很多依賴對象的構造過程,如何“更優雅”地把它們注入到待構造的實例中。

項目中,我們還需要注意一個不好的傾向——工廠模式的濫用,包括本章的工廠方法和后面要介紹的抽象工廠。有些類型雖然既不是接口,也不是抽象類,但它就是穩定,甚至比某個項目的生命周期還要長(例如:計算農歷、公歷轉換的類),盡管客戶程序依賴的是具體而不是抽象,但大部分情況下沒必要在中間建一個工廠類。

本章最后完成的工廠類型將作為本系列Java版本后續各章節普遍使用的內容,它會隨著模式的介紹不斷豐富。

4.9 Java 中的典型實現

下面的類型、方法采用了工廠方法模式,感興趣的讀者可以通過反編譯參考。

●java.lang.Class#newInstance()。

●java.lang.Integer#valueOf(String)。

●java.lang.Class#forName()。

●java.lang.reflect.Array#newInstance()。

4.10 自我檢驗

請修改本章最后完成的那個比較適于工程應用的工廠類型,使其支持含參數的構造函數。

完成后的靜態結構如圖4-5所示。

圖4-5 進一步面向工程使用需要擴展的工廠類型

主站蜘蛛池模板: 怀来县| 德昌县| 乳山市| 高要市| 罗平县| 漳平市| 林甸县| 蒙自县| 合肥市| 玉田县| 潮州市| 鄂伦春自治旗| 翼城县| 安西县| 崇仁县| 西宁市| 永嘉县| 宜宾市| 航空| 高唐县| 元谋县| 武平县| 万源市| 泽库县| 新民市| 扎兰屯市| 阿荣旗| 名山县| 江油市| 台南市| 河北省| 南昌县| 周宁县| 白山市| 游戏| 温泉县| 定襄县| 博客| 柯坪县| 思南县| 南城县|