Java與設計模式(十)創建型--工廠方法模式

簡單工廠模式雖然簡單,但存在一個很嚴重的問題。當系統中需要引入新產品時,由於靜態工廠方法通過所傳入參數的不同來創建不同的產品,這必定要修改工廠類的源代碼,將違背“開閉原則”,如何實現增加新產品而不影響已有代碼?

一、需求

比如開發一個系統運行日誌記錄器(Logger),該記錄器可以通過多種途徑保存系統的運行日誌,如通過文件記錄或數據庫記錄,用戶可以通過修改配置文件靈活地更換日誌記錄方式。在設計各類日誌記錄器時,開發人員發現需要對日誌記錄器進行一些初始化工作,初始化參數的設置過程較爲複雜,而且某些參數的設置有嚴格的先後次序,否則可能會發生記錄失敗。我們已經對簡單工廠模式有了一定的瞭解,那我們可以用簡單工廠模型寫完成這個需求。類圖如下:

LoggerFactory充當創建日誌記錄器的工廠,提供了工廠方法createLogger()用於創建日誌記錄器,Logger是抽象日誌記錄器接口,其子類爲具體日誌記錄器。其中,工廠類LoggerFactory代碼片段如下所示:

//日誌記錄器工廠  
class LoggerFactory {  
    //靜態工廠方法  
    public static Logger createLogger(String args) {  
        if(args.equalsIgnoreCase("db")) {  
            //連接數據庫,代碼省略  
            //創建數據庫日誌記錄器對象  
            Logger logger = new DatabaseLogger();   
            //初始化數據庫日誌記錄器,代碼省略  
            return logger;  
        }  
        else if(args.equalsIgnoreCase("file")) {  
            //創建日誌文件  
            //創建文件日誌記錄器對象  
            Logger logger = new FileLogger();   
            //初始化文件日誌記錄器,代碼省略  
            return logger;            
        }  
    }  
}

爲了突出設計重點,我們對上述代碼進行了簡化,省略了具體日誌記錄器類的初始化代碼。在LoggerFactory類中提供了靜態工廠方法createLogger(),用於根據所傳入的參數創建各種不同類型的日誌記錄器。通過使用簡單工廠模式,我們將日誌記錄器對象的創建和使用分離,客戶端只需使用由工廠類創建的日誌記錄器對象即可,無須關心對象的創建過程,但是我們發現,雖然簡單工廠模式實現了對象的創建和使用分離,但是仍然存在如下兩個問題:

  • 工廠類過於龐大,包含了大量的if…else…代碼,導致維護和測試難度增大(雖然只有兩行,但是如果有多種記錄日誌的方式那這裏的if….else是相當多的);
  • 系統擴展不靈活,如果增加新類型的日誌記錄器,必須修改靜態工廠方法的業務邏輯,違反了“開閉原則”。

二、工廠方法模式概述

在簡單工廠模式中只提供一個工廠類,該工廠類處於對產品類進行實例化的中心位置,它需要知道每一個產品對象的創建細節,並決定何時實例化哪一個產品類。簡單工廠模式最大的缺點是當有新產品要加入到系統中時,必須修改工廠類,需要在其中加入必要的業務邏輯,這違背了“開閉原則”。此外,在簡單工廠模式中,所有的產品都由同一個工廠創建,工廠類職責較重,業務邏輯較爲複雜,具體產品與工廠類之間的耦合度高,嚴重影響了系統的靈活性和擴展性,而工廠方法模式則可以很好地解決這一問題。

工廠方法模式定義

在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的產品對象,而是針對不同的產品提供不同的工廠,系統提供一個與產品等級結構對應的工廠等級結構。工廠方法模式定義如下:

工廠方法模式(Factory Method Pattern):定義一個用於創建對象的接口,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。工廠方法模式又簡稱爲工廠模式(Factory Pattern),又可稱作虛擬構造器模式(Virtual Constructor Pattern)或多態工廠模式(Polymorphic Factory Pattern)。工廠方法模式是一種類創建型模式。

工廠方法模式提供一個抽象工廠接口來聲明抽象工廠方法,而由其子類來具體實現工廠方法,創建具體的產品對象。工廠方法模式結構如下圖所示:

工廠方法模式中的角色

  • Product(抽象產品):它是定義產品的接口,是工廠方法模式所創建對象的超類型,也就是產品對象的公共父類。
  • ConcreteProduct(具體產品):它實現了抽象產品接口,某種類型的具體產品由專門的具體工廠創建,具體工廠和具體產品之間一一對應。+
  • Factory(抽象工廠):在抽象工廠類中,聲明瞭工廠方法(Factory Method),用於返回一個產品。抽象工廠是工廠方法模式的核心,所有創建對象的工廠類都必須實現該接口。
  • ConcreteFactory(具體工廠):它是抽象工廠類的子類,實現了抽象工廠中定義的工廠方法,並可由客戶端調用,返回一個具體產品類的實例。

工廠方法模式代碼示例

與簡單工廠模式相比,工廠方法模式最重要的區別是引入了抽象工廠角色,抽象工廠可以是接口,也可以是抽象類或者具體類,其典型代碼如下所示:

public interface Factory {
     public Product factoryMethod();  
}

在抽象工廠中聲明瞭工廠方法但並未實現工廠方法,具體產品對象的創建由其子類負責,客戶端針對抽象工廠編程,可在運行時再指定具體工廠類,具體工廠類實現了工廠方法,不同的具體工廠可以創建不同的具體產品,其典型代碼如下所示:

public class FactoryA implements Factory{
    @Override
    public Product factoryMethod() {
        return new ProductA();  
    }
}

public class FactoryB implements Factory{
    @Override
    public Product factoryMethod() {
        return new ProductB();  
    }
}

抽象產品 與具體產品代碼如下:

public interface Product {
    public void method();
}

public class ProductA implements Product{
    @Override
    public void method() {
        System.out.println("生成A產品");
    }
}

public class ProductB implements Product{
    @Override
    public void method() {
        System.out.println("生成B產品");
    }
}

在客戶端代碼中,只需關心工廠類即可,不同的具體工廠可以創建不同的產品,典型的客戶端類代碼片段如下所示:

public class Client {
    public static void main(String[] args) {
        Factory factory = new FactoryA(); //可通過配置文件實現  
        Product product = factory.factoryMethod();  
        product.method();
    }
}

三、需求優化

我們對工廠方法模式已經有了初步的瞭解,那我們根據上面的角色定義與代碼把日誌代碼改一下,重構後的類圖如下:

Logger接口充當抽象產品,其子類FileLogger和DatabaseLogger充當具體產品,LoggerFactory接口充當抽象工廠,其子類FileLoggerFactory和DatabaseLoggerFactory充當具體工廠。完整代碼如下所示:


//日誌記錄器接口:抽象產品  
interface Logger {  
    public void writeLog();  
}  

//數據庫日誌記錄器:具體產品  
class DatabaseLogger implements Logger {  
    public void writeLog() {  
        System.out.println("數據庫日誌記錄。");  
    }  
}  

//文件日誌記錄器:具體產品  
class FileLogger implements Logger {  
    public void writeLog() {  
        System.out.println("文件日誌記錄。");  
    }  
}  

//日誌記錄器工廠接口:抽象工廠  
interface LoggerFactory {  
    public Logger createLogger();  
}  

//數據庫日誌記錄器工廠類:具體工廠  
class DatabaseLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //連接數據庫,代碼省略  
            //創建數據庫日誌記錄器對象  
            Logger logger = new DatabaseLogger();   
            //初始化數據庫日誌記錄器,代碼省略  
            return logger;  
    }     
}  

//文件日誌記錄器工廠類:具體工廠  
class FileLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //創建文件日誌記錄器對象  
            Logger logger = new FileLogger();   
            //創建文件,代碼省略  
            return logger;  
    }     
}

編寫如下客戶端測試代碼:

class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        Logger logger;  
        factory = new FileLoggerFactory(); //可引入配置文件實現  
        logger = factory.createLogger();  
        logger.writeLog();  
    }  
}

四、工廠方法模式總結

工廠方法模式是簡單工廠模式的延伸,它繼承了簡單工廠模式的優點,同時還彌補了簡單工廠模式的不足。工廠方法模式是使用頻率最高的設計模式之一,是很多開源框架和API類庫的核心模式。

主要優點

工廠方法模式的主要優點如下:

  • 在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
  • 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠讓工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱爲多態工廠模式,就正是因爲所有的具體工廠類都具有同一抽象父類。
  • 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了,這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”。

主要缺點

工廠方法模式的主要缺點如下:

  • 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
  • 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度。

適用場景

在以下情況下可以考慮使用工廠方法模式:

  • 客戶端不知道它所需要的對象的類。在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建,可將具體工廠類的類名存儲在配置文件或數據庫中。
  • 抽象工廠類通過其子類來指定創建哪個對象。在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章