【學(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中,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所示:

在工廠方法模式結(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中,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所示:

引入重載方法后,抽象工廠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中,抽象工廠類(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ù)的核心模式。
- 主要優(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)閉原則”。
- 主要缺點(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)難度。
- 適用場(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上做掉