- 簡單工廠模式的不足
在簡單工廠模式中,只提供了一個工廠類,該工廠類處於對產品類進行實例化的中心位置,它知道每一個產品對象的創建細節,並決定何時實例化哪一個產品類。簡單工廠模式最大的缺點是當有新產品要加入到系統中時,必須修改工廠類,加入必要的處理邏輯,這違背了“開閉原則”。在簡單工廠模式中,所有的產品都是由同一個工廠創建,工廠類職責較重,業務邏輯較爲複雜,具體產品與工廠類之間的耦合度高,嚴重影響了系統的靈活性和擴展性,而工廠方法模式則可以很好地解決這一問題。
工廠方法模式
1、模式動機
- 考慮這樣一個系統,按鈕工廠類可以返回一個具體的按鈕實例,如圓形按鈕、矩形按鈕、菱形按鈕等。在這個系統中,如果需要增加一種新類型的按鈕,如橢圓形按鈕,那麼除了增加一個新的具體產品類之外,還需要修改工廠類的代碼,這就使得整個設計在一定程度上違反了“開閉原則”。
- 現在對該系統進行修改,不再設計一個按鈕工廠類來統一負責所有產品的創建,而是將具體按鈕的創建過程交給專門的工廠子類去完成,我們先定義一個抽象的按鈕工廠類,再定義具體的工廠類來生成圓形按鈕、矩形按鈕、菱形按鈕等,它們實現在抽象按鈕工廠類中定義的方法。這種抽象化的結果使這種結構可以在不修改具體工廠類的情況下引進新的產品,如果出現新的按鈕類型,只需要爲這種新類型的按鈕創建一個具體的工廠類就可以獲得該新按鈕的實例,這一特點無疑使得工廠方法模式具有超越簡單工廠模式的優越性,更加符合“開閉原則”。
2、模式定義
工廠方法模式(Factory Method Pattern)又稱爲工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,它屬於類創建型模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。
3、模式結構
4、模式角色
Product:抽象產品
ConcreteProduct:具體產品
Factory:抽象工廠
ConcreteFactory:具體工廠
5、模式分析
- 工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了面向對象的多態性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的創建,而是將具體創建工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的接口,而不負責哪一個產品類被實例化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
- 當系統擴展需要添加新的產品對象時,僅僅需要添加一個具體產品對象以及一個具體工廠對象,原有工廠對象不需要進行任何修改,也不需要修改客戶端,很好地符合了“開閉原則”。而簡單工廠模式在添加新產品對象後不得不修改工廠方法,擴展性不好。工廠方法模式退化後可以演變成簡單工廠模式。
- 抽象工廠類代碼
- 具體工廠類代碼
- 客戶類代碼
爲了提高系統的可擴展性和靈活性,在定義工廠和產品時都必須使用抽象層,如果需要更換產品類,只需要更換對應的工廠即可,其他代碼不需要進行任何修改。
配置文件代碼
- 在實際的應用開發中,一般將具體工廠類的實例化過程進行改進,不直接使用new關鍵字來創建對象,而是將具體類的類名寫入配置文件中,再通過Java的反射機制,讀取XML格式的配置文件,根據存儲在XML文件中的類名字符串生成對象。
- Java反射
是指在程序運行時獲取已知名稱的類或已有對象的相關信息的一種機制,包括類的方法、屬性、超類等信息,還包括實例的創建和實例類型的判斷等。可通過Class類的forName()方法返回與帶有給定字符串名的類或接口相關聯的Class對象,再通過newInstance()方法創建此對象所表示的類的一個新實例,即通過一個類名字符串得到類的實例。
- 工具類XMLUtil代碼片段
- 修改後返回客戶端的代碼
6、模式實例與分析
(1)電視機工廠
將原有的工廠進行分割,爲每種品牌的電視機提供一個子工廠,海爾工廠專門負責生產海爾電視機,海信工廠專門負責生產海信電視機,如果需要生產TCL電視機或創維電視機,只需要對應增加一個新的TCL工廠或創維工廠即可,原有的工廠無須做任何修改,使得整個系統具有更加的靈活性和可擴展性。
- 代碼實現
package 工廠方法模式3;
//抽象產品
public interface TV {
public void play();
}
package 工廠方法模式3;
//具體產品
public class HaierTV implements TV {
@Override
public void play() {
System.out.println("海爾電視機播放中。。。。");
}
}
package 工廠方法模式3;
//具體產品
public class HisenseTV implements TV {
@Override
public void play() {
System.out.println("海信電視機播放中。。。。");
}
}
package 工廠方法模式3;
//抽象工廠
public interface TVfactory {
public TV produceTV();
}
package 工廠方法模式3;
//具體工廠
public class HaierTVFactory implements TVfactory {
@Override
public TV produceTV() {
System.out.println("海爾電視機工廠生產海爾電視機");
return new HaierTV();
}
}
package 工廠方法模式3;
//具體工廠
public class HisenseTVFactory implements TVfactory {
@Override
public TV produceTV() {
System.out.println("海信電視機工廠生產海信電視機");
return new HisenseTV();
}
}
//客戶端代碼
package 工廠方法模式3;
import 工廠方法模式2.XMLUtil;
public class 電視機工廠 {
public static class client{
public static void main(String[] args) {
TV tv;
TVfactory factory;
try {
factory = (TVfactory) XMLUtil.getBean();
tv = factory.produceTV();
tv.play();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
- Config.xml文件配置:
通過XMLUtil.getBean()方法直接創建並實例化相應所需對象。減少代碼冗餘。
<?xml version="1.0"?>
<config>
<className>工廠方法模式3.HaierTVFactory</className>
</config>
- 運行結果展示:
(2)支付管理模式
- 代碼實現
package 工廠方法模式;
//抽象產品
public abstract class AbstractPay {
public abstract void pay();
}
package 工廠方法模式;
//具體產品
public class CardPay extends AbstractPay {
@Override
public void pay() {
System.out.println("銀行卡支付");
}
}
package 工廠方法模式;
//具體產品
public class CashPay extends AbstractPay {
@Override
public void pay() {
System.out.println("現金支付");
}
}
package 工廠方法模式;
//抽象工廠
public abstract class PayMethodFactory {
public abstract AbstractPay getPayMethod();
}
package 工廠方法模式;
//具體工廠
public class CardPayFactory extends PayMethodFactory {
@Override
public AbstractPay getPayMethod() {
return new CardPay();
}
}
package 工廠方法模式;
//具體工廠
public class CashPayFactory extends PayMethodFactory {
@Override
public AbstractPay getPayMethod() {
return new CashPay();
}
}
package 工廠方法模式;
import 工廠方法模式2.XMLUtil;
//客戶端代碼
public class 支付管理模式 {
public static void main (String[] args) throws Exception {
PayMethodFactory factory;
AbstractPay payMethod;
// factory = new CashPayFactory();
// payMethod = factory.getPayMethod();
//factory = new CardPayFactory();
factory = (PayMethodFactory) XMLUtil.getBean();
payMethod = factory.getPayMethod();
payMethod.pay();
}
}
- config.xml配置
<?xml version="1.0"?>
<config>
<className>工廠方法模式.CashPayFactory</className>
</config>
- 結果展示:
(3)日誌記錄器
- 代碼實現
package 工廠方法模式4;
//抽象產品
public interface Logger {
public void writeLog();
}
package 工廠方法模式4;
//具體產品
public class DatabaseLogger implements Logger {
@Override
public void writeLog() {
System.out.println("數據庫日誌記錄");
}
}
package 工廠方法模式4;
//具體產品
public class FileLogger implements Logger {
@Override
public void writeLog() {
System.out.println("文件日誌記錄");
}
}
package 工廠方法模式4;
// 抽象工廠
public interface LogFactory {
//抽象工廠方法
public Logger createLogger();
}
package 工廠方法模式4;
// 具體工廠
public class DatabaseFactory implements LogFactory {
@Override
public Logger createLogger() {
//連接數據庫,代碼省略
System.out.println("數據庫日誌工廠記錄數據庫日誌");
//創建數據庫日誌記錄器對象
//初始化數據庫日誌記錄器,代碼省略
return new DatabaseLogger();
}
}
package 工廠方法模式4;
//具體工廠
public class FileLoggerFactory implements LogFactory {
@Override
public Logger createLogger() {
System.out.println("文件日誌工廠記錄文件日誌");
return new FileLogger();
}
}
package 工廠方法模式4;
// 客戶端代碼
//public class 日誌記錄器 {
public class Client{
public static void main(String[] args) {
LogFactory factory;
Logger logger;
factory = new FileLoggerFactory();
logger = factory.createLogger();
// factory = (LogFactory) XMLUtil.getBean();
// logger =factory.createLogger();
logger.writeLog();
}
}
- Config.xml文件配置:
通過XMLUtil.getBean()方法直接創建並實例化相應所需對象。減少代碼冗餘。
<?xml version="1.0"?>
<config>
<className>工廠方法模式4.DatabaseFactory</className>
</config>
- 運行結果截圖:
同樣,也可以直接通過new FileLoggerFactory(),再調用其createLogger()方法創建對象並實例化。也可以配置在config.xml文件中,結果相同,展示結果如下所示:
7、模式的優缺點
- 優點
1、在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
2、基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱爲多態工廠模式,是因爲所有的具體工廠類都具有同一抽象父類。
3、使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則” - 缺點
1、在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的複雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
2、由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。
8、模式使用環境
1、一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
2、一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
3、將創建對象的任務委託給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。
9、模式應用
- java.util.Collection接口的iterator()方法:
- Java消息服務JMS(Java Messaging Service)
- JDBC中的工廠方法: