鍍金池/ 教程/ Java/ 工廠三兄弟之工廠方法模式(三)
工廠三兄弟之抽象工廠模式(五)
復(fù)雜對象的組裝與創(chuàng)建——建造者模式(一)
工廠三兄弟之工廠方法模式(一)
復(fù)雜對象的組裝與創(chuàng)建——建造者模式(二)
確保對象的唯一性——單例模式 (二)
工廠三兄弟之簡單工廠模式(四)
確保對象的唯一性——單例模式 (一)
工廠三兄弟之工廠方法模式(四)
對象的克隆——原型模式(一)
工廠三兄弟之抽象工廠模式(二)
工廠三兄弟之工廠方法模式(三)
工廠三兄弟之抽象工廠模式(一)
工廠三兄弟之抽象工廠模式(四)
確保對象的唯一性——單例模式 (三)
工廠三兄弟之簡單工廠模式(三)
對象的克隆——原型模式(二)
復(fù)雜對象的組裝與創(chuàng)建——建造者模式(三)
對象的克隆——原型模式(四)
確保對象的唯一性——單例模式(四)
工廠三兄弟之簡單工廠模式(一)
工廠三兄弟之簡單工廠模式(二)
對象的克隆——原型模式(三)
工廠三兄弟之抽象工廠模式(三)
確保對象的唯一性——單例模式(五)
工廠三兄弟之工廠方法模式(二)

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

完整解決方案

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

http://wiki.jikexueyuan.com/project/design-pattern-creation/images/20130712102729562.jpg" alt="日志記錄器結(jié)構(gòu)圖" />

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

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

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

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

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

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

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

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

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

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

文件日志記錄。

反射與配置文件

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

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

擴展

關(guān)于 Java 與 XML 的相關(guān)資料,大家可以閱讀 Tom Myers 和 Alexander Nakhimovsky所著的《Java XML編程指南》一書或訪問 developer Works 中國中的“Java XML 技術(shù)專題”。

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

   //通過類名生成實例對象并將其返回
   Class c=Class.forName("String");
   Object obj=c.newInstance();
   return obj;

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

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

<!— config.xml -->
<?xml version="1.0"?>
<config>
    <className>FileLoggerFactory</className>
</config>

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

//工具類XMLUtil.java
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.*;

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

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

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

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

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

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

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

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

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

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

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

思考

有人說:可以在客戶端代碼中直接通過反射機制來生成產(chǎn)品對象,在定義產(chǎn)品對象時使用抽象類型,同樣可以確保系統(tǒng)的靈活性和可擴展性,增加新的具體產(chǎn)品類無須修改源代碼,只需要將其作為抽象產(chǎn)品類的子類再修改配置文件即可,根本不需要抽象工廠類和具體工廠類。

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