創(chuàng)建型SEQ2 - 工廠方法模式 Factory Method Pattern

【學(xué)習(xí)難度:★★☆☆☆,使用頻率:★★★★★】
直接出處:工廠方法模式
梳理和學(xué)習(xí):https://github.com/BruceOuyang/boy-design-pattern
簡(jiǎn)書(shū)日期: 2018/03/05
簡(jiǎn)書(shū)首頁(yè):http://www.itdecent.cn/p/0fb891a7c5ed

工廠三兄弟之工廠方法模式(一)

簡(jiǎn)單工廠模式雖然簡(jiǎn)單,但存在一個(gè)很?chē)?yán)重的問(wèn)題。當(dāng)系統(tǒng)中需要引入新產(chǎn)品時(shí),由于靜態(tài)工廠方法通過(guò)所傳入?yún)?shù)的不同來(lái)創(chuàng)建不同的產(chǎn)品,這必定要修改工廠類(lèi)的源代碼,將違背“開(kāi)閉原則”,如何實(shí)現(xiàn)增加新產(chǎn)品而不影響已有代碼?工廠方法模式應(yīng)運(yùn)而生,本文將介紹第二種工廠模式——工廠方法模式。

1 日志記錄器的設(shè)計(jì)

Sunny軟件公司欲開(kāi)發(fā)一個(gè)系統(tǒng)運(yùn)行日志記錄器(Logger),該記錄器可以通過(guò)多種途徑保存系統(tǒng)的運(yùn)行日志,如通過(guò)文件記錄或數(shù)據(jù)庫(kù)記錄,用戶(hù)可以通過(guò)修改配置文件靈活地更換日志記錄方式。在設(shè)計(jì)各類(lèi)日志記錄器時(shí),Sunny公司的開(kāi)發(fā)人員發(fā)現(xiàn)需要對(duì)日志記錄器進(jìn)行一些初始化工作,初始化參數(shù)的設(shè)置過(guò)程較為復(fù)雜,而且某些參數(shù)的設(shè)置有嚴(yán)格的先后次序,否則可能會(huì)發(fā)生記錄失敗。如何封裝記錄器的初始化過(guò)程并保證多種記錄器切換的靈活性是Sunny公司開(kāi)發(fā)人員面臨的一個(gè)難題。

Sunny公司的開(kāi)發(fā)人員通過(guò)對(duì)該需求進(jìn)行分析,發(fā)現(xiàn)該日志記錄器有兩個(gè)設(shè)計(jì)要點(diǎn):

(1) 需要封裝日志記錄器的初始化過(guò)程,這些初始化工作較為復(fù)雜,例如需要初始化其他相關(guān)的類(lèi),還有可能需要讀取配置文件(例如連接數(shù)據(jù)庫(kù)或創(chuàng)建文件),導(dǎo)致代碼較長(zhǎng),如果將它們都寫(xiě)在構(gòu)造函數(shù)中,會(huì)導(dǎo)致構(gòu)造函數(shù)龐大,不利于代碼的修改和維護(hù);

(2) 用戶(hù)可能需要更換日志記錄方式,在客戶(hù)端代碼中需要提供一種靈活的方式來(lái)選擇日志記錄器,盡量在不修改源代碼的基礎(chǔ)上更換或者增加日志記錄方式。

Sunny公司開(kāi)發(fā)人員最初使用簡(jiǎn)單工廠模式對(duì)日志記錄器進(jìn)行了設(shè)計(jì),初始結(jié)構(gòu)如圖1所示:

圖1 基于簡(jiǎn)單工廠模式設(shè)計(jì)的日志記錄器結(jié)構(gòu)圖

在圖1中,LoggerFactory充當(dāng)創(chuàng)建日志記錄器的工廠,提供了工廠方法createLogger()用于創(chuàng)建日志記錄器,Logger是抽象日志記錄器接口,其子類(lèi)為具體日志記錄器。其中,工廠類(lèi)LoggerFactory代碼片段如下所示:

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

為了突出設(shè)計(jì)重點(diǎn),我們對(duì)上述代碼進(jìn)行了簡(jiǎn)化,省略了具體日志記錄器類(lèi)的初始化代碼。在LoggerFactory類(lèi)中提供了靜態(tài)工廠方法createLogger(),用于根據(jù)所傳入的參數(shù)創(chuàng)建各種不同類(lèi)型的日志記錄器。通過(guò)使用簡(jiǎn)單工廠模式,我們將日志記錄器對(duì)象的創(chuàng)建和使用分離,客戶(hù)端只需使用由工廠類(lèi)創(chuàng)建的日志記錄器對(duì)象即可,無(wú)須關(guān)心對(duì)象的創(chuàng)建過(guò)程,但是我們發(fā)現(xiàn),雖然簡(jiǎn)單工廠模式實(shí)現(xiàn)了對(duì)象的創(chuàng)建和使用分離,但是仍然存在如下兩個(gè)問(wèn)題:

(1) 工廠類(lèi)過(guò)于龐大,包含了大量的if…else…代碼,導(dǎo)致維護(hù)和測(cè)試難度增大;

(2) 系統(tǒng)擴(kuò)展不靈活,如果增加新類(lèi)型的日志記錄器,必須修改靜態(tài)工廠方法的業(yè)務(wù)邏輯,違反了“開(kāi)閉原則”。

如何解決這兩個(gè)問(wèn)題,提供一種簡(jiǎn)單工廠模式的改進(jìn)方案?這就是本文所介紹的工廠方法模式的動(dòng)機(jī)之一。

工廠三兄弟之工廠方法模式(二)

2 工廠方法模式概述

在簡(jiǎn)單工廠模式中只提供一個(gè)工廠類(lèi),該工廠類(lèi)處于對(duì)產(chǎn)品類(lèi)進(jìn)行實(shí)例化的中心位置,它需要知道每一個(gè)產(chǎn)品對(duì)象的創(chuàng)建細(xì)節(jié),并決定何時(shí)實(shí)例化哪一個(gè)產(chǎn)品類(lèi)。簡(jiǎn)單工廠模式最大的缺點(diǎn)是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時(shí),必須修改工廠類(lèi),需要在其中加入必要的業(yè)務(wù)邏輯,這違背了“開(kāi)閉原則”。此外,在簡(jiǎn)單工廠模式中,所有的產(chǎn)品都由同一個(gè)工廠創(chuàng)建,工廠類(lèi)職責(zé)較重,業(yè)務(wù)邏輯較為復(fù)雜,具體產(chǎn)品與工廠類(lèi)之間的耦合度高,嚴(yán)重影響了系統(tǒng)的靈活性和擴(kuò)展性,而工廠方法模式則可以很好地解決這一問(wèn)題。

在工廠方法模式中,我們不再提供一個(gè)統(tǒng)一的工廠類(lèi)來(lái)創(chuàng)建所有的產(chǎn)品對(duì)象,而是針對(duì)不同的產(chǎn)品提供不同的工廠,系統(tǒng)提供一個(gè)與產(chǎn)品等級(jí)結(jié)構(gòu)對(duì)應(yīng)的工廠等級(jí)結(jié)構(gòu)。工廠方法模式定義如下:

工廠方法模式(Factory Method Pattern):定義一個(gè)用于創(chuàng)建對(duì)象的接口,讓子類(lèi)決定將哪一個(gè)類(lèi)實(shí)例化。工廠方法模式讓一個(gè)類(lèi)的實(shí)例化延遲到其子類(lèi)。工廠方法模式又簡(jiǎn)稱(chēng)為工廠模式(Factory Pattern),又可稱(chēng)作虛擬構(gòu)造器模式(Virtual Constructor Pattern)或多態(tài)工廠模式(Polymorphic Factory Pattern)。工廠方法模式是一種類(lèi)創(chuàng)建型模式。

工廠方法模式提供一個(gè)抽象工廠接口來(lái)聲明抽象工廠方法,而由其子類(lèi)來(lái)具體實(shí)現(xiàn)工廠方法,創(chuàng)建具體的產(chǎn)品對(duì)象。工廠方法模式結(jié)構(gòu)如圖2所示:

圖2 工廠方法模式結(jié)構(gòu)圖

在工廠方法模式結(jié)構(gòu)圖中包含如下幾個(gè)角色:

  • Product(抽象產(chǎn)品):它是定義產(chǎn)品的接口,是工廠方法模式所創(chuàng)建對(duì)象的超類(lèi)型,也就是產(chǎn)品對(duì)象的公共父類(lèi)。

  • ConcreteProduct(具體產(chǎn)品):它實(shí)現(xiàn)了抽象產(chǎn)品接口,某種類(lèi)型的具體產(chǎn)品由專(zhuān)門(mén)的具體工廠創(chuàng)建,具體工廠和具體產(chǎn)品之間一一對(duì)應(yīng)。

  • Factory(抽象工廠):在抽象工廠類(lèi)中,聲明了工廠方法(Factory Method),用于返回一個(gè)產(chǎn)品。抽象工廠是工廠方法模式的核心,所有創(chuàng)建對(duì)象的工廠類(lèi)都必須實(shí)現(xiàn)該接口。

  • ConcreteFactory(具體工廠):它是抽象工廠類(lèi)的子類(lèi),實(shí)現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶(hù)端調(diào)用,返回一個(gè)具體產(chǎn)品類(lèi)的實(shí)例。

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

interface Factory {  
    public Product factoryMethod();  
}

在抽象工廠中聲明了工廠方法但并未實(shí)現(xiàn)工廠方法,具體產(chǎn)品對(duì)象的創(chuàng)建由其子類(lèi)負(fù)責(zé),客戶(hù)端針對(duì)抽象工廠編程,可在運(yùn)行時(shí)再指定具體工廠類(lèi),具體工廠類(lèi)實(shí)現(xiàn)了工廠方法,不同的具體工廠可以創(chuàng)建不同的具體產(chǎn)品,其典型代碼如下所示:

class ConcreteFactory implements Factory {  
    public Product factoryMethod() {  
        return new ConcreteProduct();  
    }  
}

在實(shí)際使用時(shí),具體工廠類(lèi)在實(shí)現(xiàn)工廠方法時(shí)除了創(chuàng)建具體產(chǎn)品對(duì)象之外,還可以負(fù)責(zé)產(chǎn)品對(duì)象的初始化工作以及一些資源和環(huán)境配置工作,例如連接數(shù)據(jù)庫(kù)、創(chuàng)建文件等。

在客戶(hù)端代碼中,只需關(guān)心工廠類(lèi)即可,不同的具體工廠可以創(chuàng)建不同的產(chǎn)品,典型的客戶(hù)端類(lèi)代碼片段如下所示:

Factory factory;  
//可通過(guò)配置文件實(shí)現(xiàn)  
factory = new ConcreteFactory(); 
Product abstractProduct;  
abstractProduct = factory.factoryMethod();  

可以通過(guò)配置文件來(lái)存儲(chǔ)具體工廠類(lèi)ConcreteFactory的類(lèi)名,更換新的具體工廠時(shí)無(wú)須修改源代碼,系統(tǒng)擴(kuò)展更為方便。

思考

工廠方法模式中的工廠方法能否為靜態(tài)方法?為什么?

工廠三兄弟之工廠方法模式(三)

3 完整解決方案

Sunny公司開(kāi)發(fā)人員決定使用工廠方法模式來(lái)設(shè)計(jì)日志記錄器,其基本結(jié)構(gòu)如圖3所示:

圖3 日志記錄器結(jié)構(gòu)圖

在圖3中,Logger接口充當(dāng)抽象產(chǎn)品,其子類(lèi)FileLogger和DatabaseLogger充當(dāng)具體產(chǎn)品,LoggerFactory接口充當(dāng)抽象工廠,其子類(lèi)FileLoggerFactory和DatabaseLoggerFactory充當(dāng)具體工廠。完整代碼如下所示:

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

//數(shù)據(jù)庫(kù)日志記錄器:具體產(chǎn)品  
class DatabaseLogger implements Logger {  
    public void writeLog() {  
        System.out.println("數(shù)據(jù)庫(kù)日志記錄。");  
    }  
}  

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

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

//數(shù)據(jù)庫(kù)日志記錄器工廠類(lèi):具體工廠  
class DatabaseLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //連接數(shù)據(jù)庫(kù),代碼省略  
            //創(chuàng)建數(shù)據(jù)庫(kù)日志記錄器對(duì)象  
            Logger logger = new DatabaseLogger();   
            //初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略  
            return logger;  
    }     
}  

//文件日志記錄器工廠類(lèi):具體工廠  
class FileLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //創(chuàng)建文件日志記錄器對(duì)象  
            Logger logger = new FileLogger();   
            //創(chuàng)建文件,代碼省略  
            return logger;  
    }     
}

編寫(xiě)如下客戶(hù)端測(cè)試代碼:

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

編譯并運(yùn)行程序,輸出結(jié)果如下:

文件日志記錄。

4 反射與配置文件

為了讓系統(tǒng)具有更好的靈活性和可擴(kuò)展性,Sunny公司開(kāi)發(fā)人員決定對(duì)日志記錄器客戶(hù)端代碼進(jìn)行重構(gòu),使得可以在不修改任何客戶(hù)端代碼的基礎(chǔ)上更換或增加新的日志記錄方式。

在客戶(hù)端代碼中將不再使用new關(guān)鍵字來(lái)創(chuàng)建工廠對(duì)象,而是將具體工廠類(lèi)的類(lèi)名存儲(chǔ)在配置文件(如XML文件)中,通過(guò)讀取配置文件獲取類(lèi)名字符串,再使用Java的反射機(jī)制,根據(jù)類(lèi)名字符串生成對(duì)象。在整個(gè)實(shí)現(xiàn)過(guò)程中需要用到兩個(gè)技術(shù):Java反射機(jī)制與配置文件讀取。軟件系統(tǒng)的配置文件通常為XML文件,我們可以使用DOM (Document Object Model)、SAX (Simple API for XML)、StAX (Streaming API for XML)等技術(shù)來(lái)處理XML文件。關(guān)于DOM、SAX、StAX等技術(shù)的詳細(xì)學(xué)習(xí)大家可以參考其他相關(guān)資料,在此不予擴(kuò)展。

擴(kuò)展

關(guān)于Java與XML的相關(guān)資料,大家可以閱讀Tom Myers和Alexander Nakhimovsky所著的《Java XML編程指南》一書(shū)或訪問(wèn)developer Works 中國(guó)中的“Java XML 技術(shù)專(zhuān)題”,參考鏈接: http://www.ibm.com/developerworks/cn/xml/theme/x-java.html

Java反射(Java Reflection)是指在程序運(yùn)行時(shí)獲取已知名稱(chēng)的類(lèi)或已有對(duì)象的相關(guān)信息的一種機(jī)制,包括類(lèi)的方法、屬性、父類(lèi)等信息,還包括實(shí)例的創(chuàng)建和實(shí)例類(lèi)型的判斷等。在反射中使用最多的類(lèi)是Class,Class類(lèi)的實(shí)例表示正在運(yùn)行的Java應(yīng)用程序中的類(lèi)和接口,其forName(String className)方法可以返回與帶有給定字符串名的類(lèi)或接口相關(guān)聯(lián)的 Class對(duì)象,再通過(guò)Class對(duì)象的newInstance()方法創(chuàng)建此對(duì)象所表示的類(lèi)的一個(gè)新實(shí)例,即通過(guò)一個(gè)類(lèi)名字符串得到類(lèi)的實(shí)例。如創(chuàng)建一個(gè)字符串類(lèi)型的對(duì)象,其代碼如下:

//通過(guò)類(lèi)名生成實(shí)例對(duì)象并將其返回  
Class c=Class.forName("String");  
Object obj=c.newInstance();  
return obj;

此外,在JDK中還提供了java.lang.reflect包,封裝了其他與反射相關(guān)的類(lèi),此處只用到上述簡(jiǎn)單的反射代碼,在此不予擴(kuò)展。

Sunny公司開(kāi)發(fā)人員創(chuàng)建了如下XML格式的配置文件config.xml用于存儲(chǔ)具體日志記錄器工廠類(lèi)類(lèi)名:

<?xml version="1.0"?>  
<config>  
    <className>FileLoggerFactory</className>  
</config>

為了讀取該配置文件并通過(guò)存儲(chǔ)在其中的類(lèi)名字符串反射生成對(duì)象,Sunny公司開(kāi)發(fā)人員開(kāi)發(fā)了一個(gè)名為XMLUtil的工具類(lèi),其詳細(xì)代碼如下所示:

//工具類(lèi)XMLUtil.java  
import javax.xml.parsers.*;  
import org.w3c.dom.*;  
import org.xml.sax.SAXException;  
import java.io.*;  

public class XMLUtil {  
//該方法用于從XML配置文件中提取具體類(lèi)類(lèi)名,并返回一個(gè)實(shí)例對(duì)象  
    public static Object getBean() {  
        try {  
            //創(chuàng)建DOM文檔對(duì)象  
            DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();  
            DocumentBuilder builder = dFactory.newDocumentBuilder();  
            Document doc;                             
            doc = builder.parse(new File("config.xml"));   

            //獲取包含類(lèi)名的文本節(jié)點(diǎn)  
            NodeList nl = doc.getElementsByTagName("className");  
            Node classNode=nl.item(0).getFirstChild();  
            String cName=classNode.getNodeValue();  

            //通過(guò)類(lèi)名生成實(shí)例對(duì)象并將其返回  
            Class c=Class.forName(cName);  
            Object obj=c.newInstance();  
            return obj;  
        }     
        catch(Exception e) {  
            e.printStackTrace();  
            return null;  
         }  
    }  
}

有了XMLUtil類(lèi)后,可以對(duì)日志記錄器的客戶(hù)端代碼進(jìn)行修改,不再直接使用new關(guān)鍵字來(lái)創(chuàng)建具體的工廠類(lèi),而是將具體工廠類(lèi)的類(lèi)名存儲(chǔ)在XML文件中,再通過(guò)XMLUtil類(lèi)的靜態(tài)工廠方法getBean()方法進(jìn)行對(duì)象的實(shí)例化,代碼修改如下:

class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        Logger logger;  
        factory = (LoggerFactory)XMLUtil.getBean(); //getBean()的返回類(lèi)型為Object,需要進(jìn)行強(qiáng)制類(lèi)型轉(zhuǎn)換  
        logger = factory.createLogger();  
        logger.writeLog();  
    }  
}

引入XMLUtil類(lèi)和XML配置文件后,如果要增加新的日志記錄方式,只需要執(zhí)行如下幾個(gè)步驟:

(1) 新的日志記錄器需要繼承抽象日志記錄器Logger;

(2) 對(duì)應(yīng)增加一個(gè)新的具體日志記錄器工廠,繼承抽象日志記錄器工廠LoggerFactory,并實(shí)現(xiàn)其中的工廠方法createLogger(),設(shè)置好初始化參數(shù)和環(huán)境變量,返回具體日志記錄器對(duì)象;

(3) 修改配置文件config.xml,將新增的具體日志記錄器工廠類(lèi)的類(lèi)名字符串替換原有工廠類(lèi)類(lèi)名字符串;

(4) 編譯新增的具體日志記錄器類(lèi)和具體日志記錄器工廠類(lèi),運(yùn)行客戶(hù)端測(cè)試類(lèi)即可使用新的日志記錄方式,而原有類(lèi)庫(kù)代碼無(wú)須做任何修改,完全符合“開(kāi)閉原則”。

通過(guò)上述重構(gòu)可以使得系統(tǒng)更加靈活,由于很多設(shè)計(jì)模式都關(guān)注系統(tǒng)的可擴(kuò)展性和靈活性,因此都定義了抽象層,在抽象層中聲明業(yè)務(wù)方法,而將業(yè)務(wù)方法的實(shí)現(xiàn)放在實(shí)現(xiàn)層中。

疑問(wèn)&思考
有人說(shuō):可以在客戶(hù)端代碼中直接通過(guò)反射機(jī)制來(lái)生成產(chǎn)品對(duì)象,在定義產(chǎn)品對(duì)象時(shí)使用抽象類(lèi)型,同樣可以確保系統(tǒng)的靈活性和可擴(kuò)展性,增加新的具體產(chǎn)品類(lèi)無(wú)須修改源代碼,只需要將其作為抽象產(chǎn)品類(lèi)的子類(lèi)再修改配置文件即可,根本不需要抽象工廠類(lèi)和具體工廠類(lèi)。

試思考這種做法的可行性?如果可行,這種做法是否存在問(wèn)題?為什么?

工廠三兄弟之工廠方法模式(四)

5 重載的工廠方法

Sunny公司開(kāi)發(fā)人員通過(guò)進(jìn)一步分析,發(fā)現(xiàn)可以通過(guò)多種方式來(lái)初始化日志記錄器,例如可以為各種日志記錄器提供默認(rèn)實(shí)現(xiàn);還可以為數(shù)據(jù)庫(kù)日志記錄器提供數(shù)據(jù)庫(kù)連接字符串,為文件日志記錄器提供文件路徑;也可以將參數(shù)封裝在一個(gè)Object類(lèi)型的對(duì)象中,通過(guò)Object對(duì)象將配置參數(shù)傳入工廠類(lèi)。此時(shí),可以提供一組重載的工廠方法,以不同的方式對(duì)產(chǎn)品對(duì)象進(jìn)行創(chuàng)建。當(dāng)然,對(duì)于同一個(gè)具體工廠而言,無(wú)論使用哪個(gè)工廠方法,創(chuàng)建的產(chǎn)品類(lèi)型均要相同。如圖4所示:

圖4 重載的工廠方法結(jié)構(gòu)圖

引入重載方法后,抽象工廠LoggerFactory的代碼修改如下:

interface LoggerFactory {  
    public Logger createLogger();  
    public Logger createLogger(String args);  
    public Logger createLogger(Object obj);  
}

具體工廠類(lèi)DatabaseLoggerFactory代碼修改如下:

class DatabaseLoggerFactory implements LoggerFactory {  
    public Logger createLogger() {  
            //使用默認(rèn)方式連接數(shù)據(jù)庫(kù),代碼省略  
            Logger logger = new DatabaseLogger();   
            //初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略  
            return logger;  
    }  

    public Logger createLogger(String args) {  
            //使用參數(shù)args作為連接字符串來(lái)連接數(shù)據(jù)庫(kù),代碼省略  
            Logger logger = new DatabaseLogger();   
            //初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略  
            return logger;  
    }     

    public Logger createLogger(Object obj) {  
            //使用封裝在參數(shù)obj中的連接字符串來(lái)連接數(shù)據(jù)庫(kù),代碼省略  
            Logger logger = new DatabaseLogger();   
            //使用封裝在參數(shù)obj中的數(shù)據(jù)來(lái)初始化數(shù)據(jù)庫(kù)日志記錄器,代碼省略  
            return logger;  
    }     
}  

//其他具體工廠類(lèi)代碼省略

在抽象工廠中定義多個(gè)重載的工廠方法,在具體工廠中實(shí)現(xiàn)了這些工廠方法,這些方法可以包含不同的業(yè)務(wù)邏輯,以滿(mǎn)足對(duì)不同產(chǎn)品對(duì)象的需求。

6 工廠方法的隱藏

有時(shí)候,為了進(jìn)一步簡(jiǎn)化客戶(hù)端的使用,還可以對(duì)客戶(hù)端隱藏工廠方法,此時(shí),在工廠類(lèi)中將直接調(diào)用產(chǎn)品類(lèi)的業(yè)務(wù)方法,客戶(hù)端無(wú)須調(diào)用工廠方法創(chuàng)建產(chǎn)品,直接通過(guò)工廠即可使用所創(chuàng)建的對(duì)象中的業(yè)務(wù)方法。

如果對(duì)客戶(hù)端隱藏工廠方法,日志記錄器的結(jié)構(gòu)圖將修改為圖5所示:

圖5 隱藏工廠方法后的日志記錄器結(jié)構(gòu)圖

在圖5中,抽象工廠類(lèi)LoggerFactory的代碼修改如下:

//改為抽象類(lèi)  
abstract class LoggerFactory {  
    //在工廠類(lèi)中直接調(diào)用日志記錄器類(lèi)的業(yè)務(wù)方法writeLog()  
    public void writeLog() {  
        Logger logger = this.createLogger();  
        logger.writeLog();  
    }  

    public abstract Logger createLogger();    
}

客戶(hù)端代碼修改如下:

class Client {  
    public static void main(String args[]) {  
        LoggerFactory factory;  
        factory = (LoggerFactory)XMLUtil.getBean();  
        factory.writeLog(); //直接使用工廠對(duì)象來(lái)調(diào)用產(chǎn)品對(duì)象的業(yè)務(wù)方法  
    }  
}

通過(guò)將業(yè)務(wù)方法的調(diào)用移入工廠類(lèi),可以直接使用工廠對(duì)象來(lái)調(diào)用產(chǎn)品對(duì)象的業(yè)務(wù)方法,客戶(hù)端無(wú)須直接使用工廠方法,在某些情況下我們也可以使用這種設(shè)計(jì)方案。

7 工廠方法模式總結(jié)

工廠方法模式是簡(jiǎn)單工廠模式的延伸,它繼承了簡(jiǎn)單工廠模式的優(yōu)點(diǎn),同時(shí)還彌補(bǔ)了簡(jiǎn)單工廠模式的不足。工廠方法模式是使用頻率最高的設(shè)計(jì)模式之一,是很多開(kāi)源框架和API類(lèi)庫(kù)的核心模式。

  1. 主要優(yōu)點(diǎn)

工廠方法模式的主要優(yōu)點(diǎn)如下:

(1) 在工廠方法模式中,工廠方法用來(lái)創(chuàng)建客戶(hù)所需要的產(chǎn)品,同時(shí)還向客戶(hù)隱藏了哪種具體產(chǎn)品類(lèi)將被實(shí)例化這一細(xì)節(jié),用戶(hù)只需要關(guān)心所需產(chǎn)品對(duì)應(yīng)的工廠,無(wú)須關(guān)心創(chuàng)建細(xì)節(jié),甚至無(wú)須知道具體產(chǎn)品類(lèi)的類(lèi)名。

(2) 基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計(jì)是工廠方法模式的關(guān)鍵。它能夠讓工廠可以自主確定創(chuàng)建何種產(chǎn)品對(duì)象,而如何創(chuàng)建這個(gè)對(duì)象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱(chēng)為多態(tài)工廠模式,就正是因?yàn)樗械木唧w工廠類(lèi)都具有同一抽象父類(lèi)。

(3) 使用工廠方法模式的另一個(gè)優(yōu)點(diǎn)是在系統(tǒng)中加入新產(chǎn)品時(shí),無(wú)須修改抽象工廠和抽象產(chǎn)品提供的接口,無(wú)須修改客戶(hù)端,也無(wú)須修改其他的具體工廠和具體產(chǎn)品,而只要添加一個(gè)具體工廠和具體產(chǎn)品就可以了,這樣,系統(tǒng)的可擴(kuò)展性也就變得非常好,完全符合“開(kāi)閉原則”。

  1. 主要缺點(diǎn)

工廠方法模式的主要缺點(diǎn)如下:

(1) 在添加新產(chǎn)品時(shí),需要編寫(xiě)新的具體產(chǎn)品類(lèi),而且還要提供與之對(duì)應(yīng)的具體工廠類(lèi),系統(tǒng)中類(lèi)的個(gè)數(shù)將成對(duì)增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,有更多的類(lèi)需要編譯和運(yùn)行,會(huì)給系統(tǒng)帶來(lái)一些額外的開(kāi)銷(xiāo)。

(2) 由于考慮到系統(tǒng)的可擴(kuò)展性,需要引入抽象層,在客戶(hù)端代碼中均使用抽象層進(jìn)行定義,增加了系統(tǒng)的抽象性和理解難度,且在實(shí)現(xiàn)時(shí)可能需要用到DOM、反射等技術(shù),增加了系統(tǒng)的實(shí)現(xiàn)難度。

  1. 適用場(chǎng)景

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

(1) 客戶(hù)端不知道它所需要的對(duì)象的類(lèi)。在工廠方法模式中,客戶(hù)端不需要知道具體產(chǎn)品類(lèi)的類(lèi)名,只需要知道所對(duì)應(yīng)的工廠即可,具體的產(chǎn)品對(duì)象由具體工廠類(lèi)創(chuàng)建,可將具體工廠類(lèi)的類(lèi)名存儲(chǔ)在配置文件或數(shù)據(jù)庫(kù)中。

(2) 抽象工廠類(lèi)通過(guò)其子類(lèi)來(lái)指定創(chuàng)建哪個(gè)對(duì)象。在工廠方法模式中,對(duì)于抽象工廠類(lèi)只需要提供一個(gè)創(chuàng)建產(chǎn)品的接口,而由其子類(lèi)來(lái)確定具體要?jiǎng)?chuàng)建的對(duì)象,利用面向?qū)ο蟮亩鄳B(tài)性和里氏代換原則,在程序運(yùn)行時(shí),子類(lèi)對(duì)象將覆蓋父類(lèi)對(duì)象,從而使得系統(tǒng)更容易擴(kuò)展。

練習(xí)

使用工廠方法模式設(shè)計(jì)一個(gè)程序來(lái)讀取各種不同類(lèi)型的圖片格式,針對(duì)每一種圖片格式都設(shè)計(jì)一個(gè)圖片讀取器,如GIF圖片讀取器用于讀取GIF格式的圖片、JPG圖片讀取器用于讀取JPG格式的圖片。需充分考慮系統(tǒng)的靈活性和可擴(kuò)展性。

練習(xí)會(huì)在我的github上做掉

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容