Java反射+簡單工廠模式總結


除了 new 之外的創建對象的方法

通過 new 創建對象,會使得程序面向實現編程,先舉個例子,某個果園里現在有兩種水果,一種是蘋果,一種是香蕉,有客戶想采摘園子里的水果,要求用get()方法表示即可

一般情況下,最直接的寫法為:

public class Apple {
    public void get() {
        System.out.println("得到蘋果");
    }
}
 
public class Banana {
    public void get() {
        System.out.println("得到香蕉");
    }
}
 
// 客戶端
public static void one() {
        // 實例化一個apple
        Apple apple = new Apple();
        // 實例化一個banana
        Banana banana = new Banana();
        apple.get();
        banana.get();
}

如上代碼,一堆的水果類,必須等到運行時才能知道實例化哪一個。一旦水果類有變化或者擴展,還要重新修改客戶端類,一旦代碼量多了,或者系統復雜了,修改的成本是很大的。

那么可以用一種方法替代,就是工廠模式——把實例化的具體代碼從應用中抽離,或者封裝。工廠模式的變形比較多,本文只引申簡單工廠模式。

簡單工廠模式

教科書的定義:

簡單工廠模式屬於類的創建型模式,也叫靜態工廠方法模式。它通過專門定義一個類來負責創建其他類的實例,目的是為了隱藏具體類的對象的創建過程,既不耽誤對象的創建,也隱藏了創建過程,被創建的實例通常都具有共同父類

繼續看水果的例子,后來果園有了新需求——用采摘到的水果做果汁,要求使用 doJuice(對應的水果)生產果汁。水果類的代碼可以保持不變,客戶端新加的其他代碼如下:

// 客戶端
private static void doJuice(Apple apple) {
    apple.get();
    System.out.println("做成果汁");
}

private static void doJuice(Banana banana) {
    banana.get();
    System.out.println("做成果汁");
}
 
public static void one() {
        // 實例化一個apple
        Apple apple = new Apple();
        // 實例化一個banana
        Banana banana = new Banana();
        doJuice(apple);
        doJuice(banana);
}

隨着業務發展,后來果園又引進了大量新水果,比如橘子,西瓜,柿子,荔枝,葡萄,哈密瓜,火龍果等。如果繼續用之前的代碼,那么除了必須新加水果類之外,在客戶端里還要分別為每一類水果添加對應的doJuice(水果)方法,然而水果那么多……使得代碼的維護性,穩定性變差

面向接口編程

為了增加程序的靈活性,可以做一些抽象,即把各個具體的水果都抽象化,可以選擇抽象類或者接口去實現,現在要創建不帶任何方法定義和成員變量的抽象化的類,首選的應該是接口

如圖1所示,接口有擴展能力,也就是舊的接口能 extends 新接口,從而使得代碼的擴展行為是可行的

使用接口的另一個原因和抽象類一樣,都是為了避免某個類被實例化,可以告訴編譯器,以及一起協作開發的程序員,這個類不需要實例化,它只是為了對某些行為做出規范,誰想用,誰就去遵守這個規則即可。

public interface Fruit {
    void get();
}
 
public class AppleA implements Fruit {
    @Override
    public void get() {
        System.out.println("蘋果");
    }
}

public class BananaA implements Fruit {
    @Override
    public void get() {
        System.out.println("香蕉");
    }
}

// 客戶端 
private static void doJuiceB(Fruit fruit) { // Fruit 是接口,只需要一個方法 doJuiceB
    fruit.get();
    System.out.println("做成果汁");
}
 
private static void two() {
        // 使用接口的引用指向子類的對象,向上轉型過程,用到了多態
        Fruit apple = new AppleA();
        Fruit banana = new BananaA();
        Fruit orange = new OrangeA();

        doJuiceB(apple);
        doJuiceB(banana);
        doJuiceB(orange);
    }

綜上,接口的作用可以概括為兩個:

1、避免客戶端去實例化某個類

2、向上轉型的使用(多態) 

分離變的部分

繼續看這個例子,客戶只是想把果園采摘的水果做出果汁,客戶作為調用者,只需要水果去做出果汁,而水果具體怎么得到的,其實客戶不需要也沒必要關心,調用者沒必要為了喝果汁還花代價去親自采摘水果……

之前的設計,客戶端有一個傳入接口類型參數的 doJuiceB(Fruit fruit); 方法。客戶端調用該方法可以動態的做出不同水果的果汁,現在把采集水果的代碼單獨放到一個類里,隱藏起來,分離變化的部分,我們叫它工廠類,下面是代碼實現。

// 工廠生產水果
// 對於客戶端來說,不再直接簡單粗暴的 new 一個水果的實例,而是把生成水果的實例的過程放到一個單獨的類,把這個實例化的過程隱藏了起來……我們叫它工廠類
public class FruitFactory {
    public static FruitC getApple() {
        return new AppleC();
    }

    public static FruitC getBanana() {
        return new BananaC();
    }
}
 
// 客戶端
private static void doJuice(FruitC fruit) {
    fruit.get();
    System.out.println("做成果汁");
}
 
private static void three() {
    FruitC apple = FruitFactory.getApple();
    FruitC banana = FruitFactory.getBanana();

    doJuice(apple);
    doJuice(banana);
}

簡單工廠模式解決的問題是如何去實例化一個合適的對象,簡單工廠模式的核心思想就是有一個專門的類來負責創建實例。具體來說,把產品看為是一系列的類的集合,這些類由某個抽象類或者接口派生出一個對象樹,工廠類產生一個合適的對象來滿足客戶的要求,從而把對象的創建過程進行封裝

如果簡單工廠模式所涉及到的具體產品之間沒有共同的邏輯,那么就可以使用接口來扮演抽象產品的角色,如果具體產品之間有邏輯上的聯系,就把這些共同的東西提取出來,放在一個抽象類中,然后讓具體產品繼承抽象類,以實現代碼復用,如圖2所示。借用高斯林(Java之父)所說:

共同的東西總是應該抽象出來。在實際的的使用中,抽象產品和具體產品之間往往是多層次的產品結構

引入簡單工廠模式

上面的設計,對於客戶端來說,不再直接簡單粗暴的 new 一個水果的實例,而是把生成水果的實例的過程放到一個單獨的類,把這個實例化的過程隱藏了起來……我們叫它工廠類,這個設計也叫簡單工廠模式——它解決的問題是如何去實例化一個合適的對象。

簡單工廠模式的核心思想就是:有一個專門的類來負責創建實例。具體來說,把產品看着是一系列的類的集合,這些類是由某個抽象類或者接口派生出來的一個對象樹,而工廠類用來產生一個合適的對象來滿足客戶的要求,從而把對象的創建過程進行封裝,如果簡單工廠模式所涉及到的具體產品之間沒有共同的邏輯,那么我們就可以使用接口來扮演抽象產品的角色,如果具體產品之間有邏輯上的聯系,我們就把這些共同的東西提取出來,放在一個抽象類中,然后讓具體產品繼承抽象類,為實現更好復用的目的,共同的東西總是應該抽象出來的。在實際的的使用中,抽象產品和具體產品之間往往是多層次的產品結構,如圖:

下面看看教科書的定義:簡單工廠模式屬於類的創建型模式,也叫靜態工廠方法模式,通過專門定義一個類來負責創建其他類的實例,目的是為了隱藏具體類的對象的創建過程,既不耽誤對象的創建,也隱藏了創建過程。被創建的實例通常都具有共同父類

本例子里,蘋果和香蕉都有一個共同的父類——水果,此時我們專門定義一個類,負責創建其他類的實例,這個類叫簡單工廠類,它有三個角色:

1、工廠(Creator)角色 :簡單工廠模式的核心,它負責實現創建所有實例的內部邏輯。工廠類可以被外界直接調用,創建所需的產品對象。

2、抽象產品(Product)角色: 簡單工廠模式所創建的所有對象的父類,它負責描述所有實例所共有的公共接口,或者抽象類。

3.具體產品(Concrete Product)角色: 簡單工廠模式所創建的具體實例對象,這些對象去繼承或者實現抽象角色

不過,細細體味下,在工廠類里針對每一個水果都有一個對應的獲取水果的操作,這是一種很粗糙的設計,還可以更好,就是把每個get方法抽象為一個公用的get方法,代碼如下:

public interface FruitD {
    void get();
}
//////////////////////////////
public class AppleD implements FruitD {
    @Override
    public void get() {
        System.out.println("蘋果");
    }
}
///////////////////////////////
public class BananaD implements FruitD {
    @Override
    public void get() {
        System.out.println("香蕉");
    }
}
//////////////////////////////
public class FruitFactoryFour {
    public static FruitD getFruit(String type) {
        if ("apple".equalsIgnoreCase(type)) {
            return new AppleD();
        } else if ("banana".equalsIgnoreCase(type)) {
            return new BananaD();
        } else {
            System.out.print("error!");
        }

        return null;
    }
}

這樣稍微好了點兒,把每個水果對應的get方法抽象為一個公用的get方法,工廠類里根據傳入的參數,去判斷應該生成哪個水果的對象,並把這個對象返回(依然是向上轉型的使用),客戶端只需簡單的進行調用即可。

非常方便,也隱藏了具體產品的實例化過程,完美的完成了客戶和水果廠的需求。

可以認為簡單工廠模式的核心是工廠類,這個類含有必要的邏輯判斷(if-else),可以決定在什么時候創建哪一個類的實例,而調用者則可以免除直接創建對象的責任。簡單工廠模式通過這種做法實現了對責任的分割,當系統引入新的產品的時候無需修改調用者。

解耦合的簡單工廠模式

雖然簡單工廠模式分離了產品的創建者和消費者,有利於軟件系統結構的優化,但是由於一切產品創建的業務邏輯都集中在一個工廠類中,導致了沒有很高的內聚性,同時也違背了開閉原則。另外,簡單工廠模式的方法一般都是靜態的,而靜態工廠方法讓子類繼承是可能被隱藏的,因此,簡單工廠模式無法形成基於基類的繼承樹結構。

到了這里,其實又要想,不要過度的優化,不要為了使用設計模式而使用設計模式,如果是業務比較簡單的場景,這樣的簡單工廠模式還是非常好用的。但無論如何,繁瑣的if-else判斷還是不太好,一旦判斷條件稍微多點兒,if-else寫起來就非常繁瑣。

觀察一些開源框架實現類似場景的代碼,發現它們使用了 Java 的反射機制省去了判斷的步驟,比之前的繁瑣的 if-else 判斷要好一些,如下代碼。

public interface FruitE {
    void get();
}

public class BananaE implements FruitE {
    @Override
    public void get() {
        System.out.println("香蕉");
    }
}

public class AppleE implements FruitE {
    @Override
    public void get() {
        System.out.println("蘋果");
    }
}
 
// 新的工廠類
public class FruitFactoryFive {
        public static FruitE getFruit(String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        Class fruit = Class.forName(type);
        return (FruitE) fruit.newInstance();
    }
}
 
// 客戶端
private static void five() throws IllegalAccessException, InstantiationException, ClassNotFoundException {
        FruitE apple = FruitFactoryFive.getFruit("simpleFactory.five.AppleE");
        FruitE banana = FruitFactoryFive.getFruit("simpleFactory.five.BananaE");

        apple.get();
        banana.get();
}

如此一來,使得工廠的擴展性變強了。

補充:forName 方法和 newInstance 方法

從 JVM 的角度看,使用 new 的時候,這個要 new 的類可以沒有被 JVM 加載,但是使用 newInstance,就必須保證這個類已經加載且這個類已經鏈接,而完成這兩個步驟的正是 Class 的靜態方法 forName(......),該方法調用了啟動類加載器(bootstrap加載器)去加載類(不初始化)。

Class 類的對象方法 newInstance 與靜態方法 forName 實際上是把 new 關鍵字做的事情分解為了兩步:

1、加載某個類

2、初始化

這樣分步調用構造器的好處是顯而易見的,因為它的粒度更細,所以程序可以在實例化類的時候獲得更好的靈活性,催生一些降耦手段。

事實上,Class 類的 newinstance 方法經常被各種框架使用,它是解耦合的利器之一,比如設計模式中最最常見的工廠模式。

當然,一些知名的開源框架使用了更高級的asm等字節碼框架,能使反射操作的性能非常高效,並且還能修改已經編譯的字節碼,使得程序的靈活性變得很強。

依托配置文件(注解)完全解耦

但是依然不完美—客戶端缺少調用的靈活性,客戶端必須傳入嚴格對應類名的字符串,甚至還要包含完整的包名,才能實例化對應的類,稍微差一點兒,都會失敗。故還是前面說的,簡單的業務一般使用if-else的方式傳入字符串即可,而稍微復雜的,可以變為反射的方式實現,而反射實現工廠類,對於客戶端又顯得調用上不方便。一些開源框架使用了配置文件或者注解解決了該問題,現在Java世界的主流是約定優於配置,注解是主流。

String className = readConfig(); // 從配置文件中獲得類的句柄
Class c = Class.forName(className);
factory = (FruitE)c.newInstance();

利用配置文件消滅了寫死的產品類名稱,無論產品怎么變化,代碼不會再修改,甚至可以更換類的子類,只要他們繼承該類(實現接口)就可以。

當然進一步就是自定義注解處理器,實現自己系統的注解

簡單工廠模式的經典案例——JDBC

JDBC是SUN公司提供的一套數據庫編程接口。它能提供簡單、一致的方式訪問各種關系型數據庫。Java通過JDBC可以執行SQL語句,並能對獲取的數據進行處理,將變化了的數據存回數據庫。用JDBC進行數據庫訪問時,要使用數據庫廠商提供的驅動程序接口與DBMS進行交互。客戶端要使用使用數據時,只需要和工廠交互即可,這就是典型的簡單工廠模式的應用。使得程序員的代碼量得到極大的簡化。

操作步驟按照順序依次為:

1、注冊並加載數據庫驅動,一般使用Class.forName();

2、創建與數據庫的鏈接Connection對象

3、創建SQL語句對象preparedStatement(sql);

4、提交SQL語句,根據實際情況使用executeQuery()或者executeUpdate();

5、顯示相應的結果

6、關閉數據庫

簡單工廠模式的優缺點

優點

工廠類是整個模式的關鍵所在,它包含必要的判斷邏輯,能夠根據外界給定的信息,決定究竟應該創建哪個具體類的對象。

用戶在使用時可以直接根據工廠類去創建所需的實例,而無需了解這些對象是如何創建以及如何組織的,有利於整個軟件體系結構的優化

缺點

由於工廠類集中了所有實例的創建邏輯,這就直接導致一旦這個工廠出了問題,所有的客戶端都會受到牽連;

由於簡單工廠模式的產品基於一個共同的抽象類或者接口,這樣一來,產品的種類增加的時候,即有不同的產品接口或者抽象類的時候,工廠類就需要判斷何時創建何種種類的產品,這就和創建何種種類產品的產品相互混淆在了一起,違背了單一職責,導致系統喪失靈活性和可維護性。

簡單工廠模式違背了“開放封閉原則”,因為當新增加一個產品的時候必須修改工廠類,相應的工廠類就需要重新編譯一遍。

一句話:雖然簡單工廠模式分離產品的創建者和消費者,有利於軟件系統結構的優化,但由於一切邏輯都集中在一個工廠類中,導致了沒有很高的內聚性,同時也違背了“開放封閉原則”。另外,簡單工廠模式的方法一般都是靜態的,而靜態工廠方法是無法讓子類繼承的,因此,簡單工廠模式無法形成基於基類的繼承樹結構

引申: Java 生成對象的方法都有哪些?

Java中有5類創建對象的方式

1、new

2、反射,Class.newInstance()或Contructor.newInstance(),其本質是一樣的,都采用了反射機制

3、clone方法

4、反序列化

5、JNI 

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM