工廠方法模式
介紹
簡單工廠模式雖然簡單当纱,并且實現(xiàn)了創(chuàng)建和使用分離的功能呛每,但存在一個很嚴重的問題。當系統(tǒng)中需要引入新產品時坡氯,由于靜態(tài)工廠方法通過所傳入?yún)?shù)的不同來創(chuàng)建不同的產品晨横,這必定要修改工廠類的源代碼洋腮,將違背“開閉原則”,并且工廠類可能過于龐大手形,出現(xiàn)大量if...else...的語句徐矩,導致維護和測試難度增大,如何實現(xiàn)增加新產品而不影響已有代碼叁幢?工廠方法模式應運而生,本文將介紹第二種工廠模式——工廠方法模式坪稽。
在簡單工廠模式中只提供一個工廠類曼玩,該工廠類處于對產品類進行實例化的中心位置,它需要知道每一個產品對象的創(chuàng)建細節(jié)窒百,并決定何時實例化哪一個產品類黍判。簡單工廠模式最大的缺點是當有新產品要加入到系統(tǒng)中時,必須修改工廠類篙梢,需要在其中加入必要的業(yè)務邏輯顷帖,這違背了“開閉原則”。此外渤滞,在簡單工廠模式中贬墩,所有的產品都由同一個工廠創(chuàng)建,工廠類職責較重妄呕,業(yè)務邏輯較為復雜陶舞,具體產品與工廠類之間的耦合度高,嚴重影響了系統(tǒng)的靈活性和擴展性绪励,而工廠方法模式則可以很好地解決這一問題肿孵。
在工廠方法模式中,我們不再提供一個統(tǒng)一的工廠類來創(chuàng)建所有的產品對象疏魏,而是針對不同的產品提供不同的工廠停做,系統(tǒng)提供一個與產品等級結構對應的工廠等級結構。
工廠方法模式定義
定義一個用于創(chuàng)建對象的接口大莫,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類只厘。工廠方法模式又簡稱為工廠模式(Factory Pattern),又可稱作虛擬構造器模式(Virtual Constructor Pattern)或多態(tài)工廠模式(Polymorphic Factory Pattern)懈凹。工廠方法模式是一種類創(chuàng)建型模式。
工廠方法模式UML類圖
角色介紹
- Product(抽象產品):它是定義產品的接口介评,是工廠方法模式所創(chuàng)建對象的超類型库北,也就是產品對象的公共父類爬舰。
- ConcreteProduct(具體產品):它實現(xiàn)了抽象產品接口寒瓦,某種類型的具體產品由專門的具體工廠創(chuàng)建情屹,具體工廠和具體產品之間一一對應杂腰。
- Factory(抽象工廠):在抽象工廠類中垃你,聲明了工廠方法(Factory Method),用于返回一個產品喂很。抽象工廠是工廠方法模式的核心惜颇,所有創(chuàng)建對象的工廠類都必須實現(xiàn)該接口。
- ConcreteFactory(具體工廠):它是抽象工廠類的子類凌摄,實現(xiàn)了抽象工廠中定義的工廠方法,并可由客戶端調用锨亏,返回一個具體產品類的實例。
與簡單工廠模式的區(qū)別
最重要的區(qū)別就是引入了抽象工廠角色器予,抽象工廠可以是接口,也可以是抽象類或者具體類劣摇。
在抽象工廠中聲明了工廠方法但并未實現(xiàn)工廠方法,具體產品對象的創(chuàng)建由其子類負責末融,客戶端針對抽象工廠編程,可在運行時再指定具體工廠類勾习,具體工廠類實現(xiàn)了工廠方法,不同的具體工廠可以創(chuàng)建不同的具體產品巧婶。
interface Factory {
public Product factoryMethod();
}
class ConcreteFactory implements Factory {
public Product factoryMethod() {
return new ConcreteProduct();
}
}
// 客戶端創(chuàng)建產品
Factory factory;
factory = new ConcreteFactory(); //可通過配置文件實現(xiàn)
Product product;
product = factory.factoryMethod();
demo
使用工廠模式設計日志記錄器涂乌。
Logger接口充當抽象產品艺栈,其子類FileLogger和DatabaseLogger充當具體產品湾盒,LoggerFactory接口充當抽象工廠,其子類FileLoggerFactory和DatabaseLoggerFactory充當具體工廠罚勾。
//日志記錄器接口:抽象產品
interface Logger {
public void writeLog();
}
//數(shù)據(jù)庫日志記錄器:具體產品
class DatabaseLogger implements Logger {
public void writeLog() {
System.out.println("數(shù)據(jù)庫日志記錄吭狡。");
}
}
//文件日志記錄器:具體產品
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();
}
}
優(yōu)化代碼
動態(tài)加載工廠類
為了讓系統(tǒng)具有更好的靈活性和可擴展性俐载,不再使用new關鍵字來創(chuàng)建工廠對象,而是將具體工廠類名存儲在配置文件中瞎疼,通過讀取配置文件獲取類名字符串,再反射出具體的類壁畸。
重載工廠方法
工廠方法可能會存在多種途徑來創(chuàng)建產品贼急,比如日志記錄系統(tǒng),可以從文件中讀取捏萍,也可以從數(shù)據(jù)庫讀取太抓。
在抽象工廠中定義多個重載的工廠方法,在具體工廠中實現(xiàn)了這些工廠方法令杈,這些方法可以包含不同的業(yè)務邏輯走敌,以滿足對不同產品對象的需求。
引入重載方法后逗噩,抽象工廠LoggerFactory的代碼修改如下:
interface LoggerFactory {
public Logger createLogger();
public Logger createLogger(String args);
public Logger createLogger(Object obj);
}
工廠方法的隱藏
有時候,為了進一步簡化客戶端的使用异雁,還可以對客戶端隱藏工廠方法赃额,此時李剖,在工廠類中將直接調用產品類的業(yè)務方法锭部,客戶端無須調用工廠方法創(chuàng)建產品,直接通過工廠即可使用所創(chuàng)建的對象中的業(yè)務方法拌禾。
//改為抽象類
abstract class LoggerFactory {
//在工廠類中直接調用日志記錄器類的業(yè)務方法writeLog()
public void writeLog() {
Logger logger = this.createLogger();
logger.writeLog();
}
public abstract Logger createLogger();
}
class Client {
public static void main(String args[]) {
LoggerFactory factory;
factory = (LoggerFactory)XMLUtil.getBean();
factory.writeLog(); //直接使用工廠對象來調用產品對象的業(yè)務方法
}
}
Cocoa框架中的使用
NSNumber
盡管NSNumber可以使用常見的alloc init方式創(chuàng)建NSNumber展哭,但這樣創(chuàng)建出來的對象仍然是nil扼菠,只能使用預先定義的工廠方法來創(chuàng)建有意義的實例。例如
[NSNumber numberWithBool:YES]
此消息會得到一個NSNumber的子類NSCFBoolean的一個實例循榆。
NSNumber就是工廠模式的一個變體墨坚,NSNumber本身是一個抽象父類,直接由抽象類生成具體子類
總結
優(yōu)點
- 在工廠方法模式中泽篮,工廠方法用來創(chuàng)建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節(jié)帽撑,用戶只需要關心所需產品對應的工廠,無須關心創(chuàng)建細節(jié)亏拉,甚至無須知道具體產品類的類名。
- 基于工廠角色和產品角色的多態(tài)性設計是工廠方法模式的關鍵莽使。它能夠讓工廠可以自主確定創(chuàng)建何種產品對象,而如何創(chuàng)建這個對象的細節(jié)則完全封裝在具體工廠內部芳肌。工廠方法模式之所以又被稱為多態(tài)工廠模式肋层,就正是因為所有的具體工廠類都具有同一抽象父類亿笤。
- 使用工廠方法模式的另一個優(yōu)點是在系統(tǒng)中加入新產品時栋猖,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端掂铐,也無須修改其他的具體工廠和具體產品罕拂,而只要添加一個具體工廠和具體產品就可以了全陨,這樣,系統(tǒng)的可擴展性也就變得非常好辱姨,完全符合“開閉原則”。
缺點
- 在添加新產品時枢舶,需要編寫新的具體產品類懦胞,而且還要提供與之對應的具體工廠類凉泄,系統(tǒng)中類的個數(shù)將成對增加,在一定程度上增加了系統(tǒng)的復雜度后众,有更多的類需要編譯和運行,會給系統(tǒng)帶來一些額外的開銷蒂誉。
- 由于考慮到系統(tǒng)的可擴展性,需要引入抽象層括堤,在客戶端代碼中均使用抽象層進行定義,增加了系統(tǒng)的抽象性和理解難度悄窃,且在實現(xiàn)時可能需要用到DOM登夫、反射等技術广匙,增加了系統(tǒng)的實現(xiàn)難度恼策。
適用場景
- 客戶端不知道它所需要的對象的類(編譯時無法準確預期要創(chuàng)建的對象的類)潮剪。在工廠方法模式中,客戶端不需要知道具體產品類的類名抗碰,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創(chuàng)建弧蝇,可將具體工廠類的類名存儲在配置文件或數(shù)據(jù)庫中。
- 抽象工廠類通過其子類來指定創(chuàng)建哪個對象(類想讓其子類決定在運行時創(chuàng)建什么)沙峻。在工廠方法模式中两芳,對于抽象工廠類只需要提供一個創(chuàng)建產品的接口摔寨,而由其子類來確定具體要創(chuàng)建的對象怖辆,利用面向對象的多態(tài)性和里氏代換原則删顶,在程序運行時淑廊,子類對象將覆蓋父類對象,從而使得系統(tǒng)更容易擴展蒋纬。