工廠方法模式(JAVA反射)


簡單工廠模式的不足
    在簡單工廠模式中,只提供了一個工廠類,該工廠類處於對產品類進行實例化的中心位置,它知道每一個產品對象的創建細節,並決定何時實例化哪一個產品類。簡單工廠模式最大的缺點是當有新產品要加入到系統中時,必須修改工廠類,加入必要的處理邏輯,這違背了“開閉原則”。在簡單工廠模式中,所有的產品都是由同一個工廠創建,工廠類職責較重,業務邏輯較為復雜,具體產品與工廠類之間的耦合度高,嚴重影響了系統的靈活性和擴展性,而工廠方法模式則可以很好地解決這一問題。

模式動機

    考慮這樣一個系統,按鈕工廠類可以返回一個具體的按鈕實例,如圓形按鈕、矩形按鈕、菱形按鈕等。在這個系統中,如果需要增加一種新類型的按鈕,如橢圓形按鈕,那么除了增加一個新的具體產品類之外,還需要修改工廠類的代碼,這就使得整個設計在一定程度上違反了“開閉原則”。
    現在對該系統進行修改,不再設計一個按鈕工廠類來統一負責所有產品的創建,而是將具體按鈕的創建過程交給專門的工廠子類去完成,我們先定義一個抽象的按鈕工廠類,再定義具體的工廠類來生成圓形按鈕、矩形按鈕、菱形按鈕等,它們實現在抽象按鈕工廠類中定義的方法。這種抽象化的結果使這種結構可以在不修改具體工廠類的情況下引進新的產品,如果出現新的按鈕類型,只需要為這種新類型的按鈕創建一個具體的工廠類就可以獲得該新按鈕的實例,這一特點無疑使得工廠方法模式具有超越簡單工廠模式的優越性,更加符合“開閉原則”。
使用工廠方法模式設計的按鈕工廠:

模式定義

    工廠方法模式(Factory Method Pattern)又稱為工廠模式,也叫虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,它屬於類創建型模式。在工廠方法模式中,工廠父類負責定義創建產品對象的公共接口,而工廠子類則負責生成具體的產品對象,這樣做的目的是將產品類的實例化操作延遲到工廠子類中完成,即通過工廠子類來確定究竟應該實例化哪一個具體產品類。

模式結構

工廠方法模式包含如下角色:
    •  Product:抽象產品
    •  ConcreteProduct:具體產品
    •  Factory:抽象工廠
    •  ConcreteFactory:具體工廠

模式分析

    工廠方法模式是簡單工廠模式的進一步抽象和推廣。由於使用了面向對象的多態性,工廠方法模式保持了簡單工廠模式的優點,而且克服了它的缺點。在工廠方法模式中,核心的工廠類不再負責所有產品的創建,而是將具體創建工作交給子類去做。這個核心類僅僅負責給出具體工廠必須實現的接口,而不負責哪一個產品類被實例化這種細節,這使得工廠方法模式可以允許系統在不修改工廠角色的情況下引進新產品。
    當系統擴展需要添加新的產品對象時,僅僅需要添加一個具體產品對象以及一個具體工廠對象,原有工廠對象不需要進行任何修改,也不需要修改客戶端,很好地符合了“開閉原則”。而簡單工廠模式在添加新產品對象后不得不修改工廠方法,擴展性不好。工廠方法模式退化后可以演變成簡單工廠模式。
抽象工廠類代碼:
1 public abstract class PayMethodFactory
2 {
3     public abstract AbstractPay getPayMethod();
4 }

 具體工廠類代碼:

1 public class CashPayFactory extends PayMethodFactory
2 {
3     public AbstractPay getPayMethod()
4     {
5         return new CashPay();
6     }
7 } 

 客戶類代碼片段:

1 PayMethodFactory factory;
2 AbstractPay payMethod;
3 factory=new CashPayFactory();
4 payMethod =factory.getPayMethod();
5 payMethod.pay(); 
   為了提高系統的可擴展性和靈活性,在定義工廠和產品時都必須使用抽象層,如果需要更換產品類,只需要更換對應的工廠即可,其他代碼不需要進行任何修改。
配置文件代碼:
    • 在實際的應用開發中,一般將具體工廠類的實例化過程進行改進,不直接使用new關鍵字來創建對象,而是將具體類的類名寫入配置文件中,再通過Java的反射機制,讀取XML格式的配置文件,根據存儲在XML文件中的類名字符串生成對象。
1 <?xml version="1.0"?>
2 <config>
3   <className>CashPayFactory</className>
4 </config>
Java反射(Java Reflection):
    • 是指在程序運行時獲取已知名稱的類或已有對象的相關信息的一種機制,包括類的方法、屬性、超類等信息,還包括實例的創建和實例類型的判斷等。可通過Class類的forName()方法返回與帶有給定字符串名的類或接口相關聯的Class對象,再通過newInstance()方法創建此對象所表示的類的一個新實例,即通過一個類名字符串得到類的實例。
1 //創建一個字符串類型的對象
2 Class c = Class.forName(“String”);
3 Object obj = c.newInstance();
4 return obj;

工具類XMLUtil代碼片段:

 1 //創建DOM文檔對象
 2 DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
 3 DocumentBuilder builder = dFactory.newDocumentBuilder();
 4 Document doc;                            
 5 doc = builder.parse(new File("config.xml")); 
 6 
 7 //獲取包含類名的文本節點
 8 NodeList nl = doc.getElementsByTagName("className");
 9 Node classNode=nl.item(0).getFirstChild();
10 String cName=classNode.getNodeValue();
11             
12  //通過類名生成實例對象並將其返回
13 Class c=Class.forName(cName);
14 Object obj=c.newInstance();
15 return obj;

模式實例與解析

實例一:電視機工廠
    • 將原有的工廠進行分割,為每種品牌的電視機提供一個子工廠,海爾工廠專門負責生產海爾電視機,海信工廠專門負責生產海信電視機,如果需要生產TCL電視機或創維電視機,只需要對應增加一個新的TCL工廠或創維工廠即可,原有的工廠無須做任何修改,使得整個系統具有更加的靈活性和可擴展性。

實例代碼(JAVA):

  1 //抽象產品類 TV
  2 public interface TV
  3 {
  4     public void play();
  5 }
  6 
  7 //具體產品類 HaierTV
  8 public class HaierTV implements TV
  9 {
 10     public void play()
 11     {
 12         System.out.println("海爾電視機播放中......");
 13     }
 14 }
 15 
 16 //具體產品類 HisenseTV
 17 public class HisenseTV implements TV
 18 {
 19     public void play()
 20     {
 21         System.out.println("海信電視機播放中......");
 22     }    
 23 }
 24 
 25 //抽象工廠類 TVFactory
 26 public interface TVFactory
 27 {
 28     public TV produceTV();
 29 }
 30 
 31 //具體工廠類 HaierTVFactory
 32 public class HaierTVFactory implements TVFactory
 33 {
 34     public TV produceTV()
 35     {
 36         System.out.println("海爾電視機工廠生產海爾電視機...");
 37         return new HaierTV();
 38     }
 39 }
 40 
 41 //具體工廠類 HisenseTVFactory
 42 public class HisenseTVFactory implements TVFactory
 43 {
 44     public TV produceTV()
 45     {
 46         System.out.println("海信電視機工廠生產海信電視機...");
 47         return new HisenseTV();
 48     }
 49 }
 50 
 51 //配置文件 config.xml
 52 <?xml version="1.0"?>
 53 <config>
 54     <className>HisenseTVFactory</className>
 55 </config>
 56 
 57 //通過反射獲得具體工廠的實例 XMLUtil
 58 import javax.xml.parsers.*;
 59 import org.w3c.dom.*;
 60 import org.xml.sax.SAXException;
 61 import java.io.*;
 62 public class XMLUtil
 63 {
 64 //該方法用於從XML配置文件中提取具體類類名,並返回一個實例對象
 65     public static Object getBean()
 66     {
 67         try
 68         {
 69             //創建文檔對象
 70             DocumentBuilderFactory dFactory = DocumentBuilderFactory.newInstance();
 71             DocumentBuilder builder = dFactory.newDocumentBuilder();
 72             Document doc;                            
 73             doc = builder.parse(new File("config.xml")); 
 74         
 75             //獲取包含類名的文本節點
 76             NodeList nl = doc.getElementsByTagName("className");
 77             Node classNode=nl.item(0).getFirstChild();
 78             String cName=classNode.getNodeValue();
 79             
 80             //通過類名生成實例對象並將其返回
 81             Class c=Class.forName(cName);
 82               Object obj=c.newInstance();
 83             return obj;
 84            }   
 85                catch(Exception e)
 86                {
 87                    e.printStackTrace();
 88                    return null;
 89                }
 90         }
 91 }
 92 
 93 //客戶端 Client
 94 public class Client
 95 {
 96     public static void main(String args[])
 97     {
 98          try
 99          {
100              TV tv;
101              TVFactory factory;
102              factory=(TVFactory)XMLUtil.getBean();
103              tv=factory.produceTV();
104              tv.play();
105          }
106          catch(Exception e)
107          {
108              System.out.println(e.getMessage());
109          }
110     }
111 }

 實例代碼(C++):

 1 // 工廠方法模式
 2 #include <iostream>
 3 using namespace std;
 4 
 5 //抽象產品TV
 6 class TV
 7 {
 8 public:
 9     virtual void play() = 0;
10 };
11 
12 //具體產品類 HaierTV
13 class HaierTV:public TV
14 {
15 public:
16     void play() override
17     {
18         cout << "海爾電視機播放中..." << endl;
19     }
20 };
21 
22 //具體產品類HisenseTV
23 class HisenseTV:public TV
24 {
25 public:
26     void play() override
27     {
28         cout << "海信電視機播放中..." << endl;
29     }
30 };
31 
32 //抽象工廠類 TVFactory
33 class TVFactory
34 {
35 public:
36     virtual TV* productTV() = 0;
37 };
38 
39 //具體工廠類 HaierTVFactory
40 class HaierTVFactory:public TVFactory
41 {
42 public:
43     TV* productTV() override
44     {
45         cout << "海爾工廠生產海爾電視機..." << endl;
46         return new HaierTV();
47     }
48 };
49 
50 //具體工廠類 HisenseTVFactory
51 class HisenseTVFactory:public TVFactory
52 {
53 public:
54     TV* productTV() override
55     {
56         cout << "海信工廠生產海信電視機..." << endl;
57         return new HisenseTV();
58     }
59 };
60 
61 //客戶端
62 int main()
63 {
64     TV* tv = nullptr;
65     TVFactory* factory = nullptr;
66     factory = new HaierTVFactory();
67     tv = factory->productTV();
68     tv->play();
69     factory = new HisenseTVFactory();
70     tv = factory->productTV();
71     tv->play();
72     return 0;
73 }

 運行結果:

模式優缺點

優點
    • 在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節,用戶只需要關心所需產品對應的工廠,無須關心創建細節,甚至無須知道具體產品類的類名。
    • 基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵。它能夠使工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱為多態工廠模式,是因為所有的具體工廠類都具有同一抽象父類。
    • 使用工廠方法模式的另一個優點是在系統中加入新產品時,無須修改抽象工廠和抽象產品提供的接口,無須修改客戶端,也無須修改其他的具體工廠和具體產品,而只要添加一個具體工廠和具體產品就可以了。這樣,系統的可擴展性也就變得非常好,完全符合“開閉原則”。
缺點
    • 在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,系統中類的個數將成對增加,在一定程度上增加了系統的復雜度,有更多的類需要編譯和運行,會給系統帶來一些額外的開銷。
    • 由於考慮到系統的可擴展性,需要引入抽象層,在客戶端代碼中均使用抽象層進行定義,增加了系統的抽象性和理解難度,且在實現時可能需要用到DOM、反射等技術,增加了系統的實現難度。

模式適用環境

在以下情況下可以使用工廠方法模式:
    • 一個類不知道它所需要的對象的類:在工廠方法模式中,客戶端不需要知道具體產品類的類名,只需要知道所對應的工廠即可,具體的產品對象由具體工廠類創建;客戶端需要知道創建具體產品的工廠類。
    • 一個類通過其子類來指定創建哪個對象:在工廠方法模式中,對於抽象工廠類只需要提供一個創建產品的接口,而由其子類來確定具體要創建的對象,利用面向對象的多態性和里氏代換原則,在程序運行時,子類對象將覆蓋父類對象,從而使得系統更容易擴展。
    • 將創建對象的任務委托給多個工廠子類中的某一個,客戶端在使用時可以無須關心是哪一個工廠子類創建產品子類,需要時再動態指定,可將具體工廠類的類名存儲在配置文件或數據庫中。

模式應用

(1) java.util.Collection接口的iterator()方法:

 

(2) Java消息服務JMS(Java Messaging Service) :

 1 //使用上下文和JNDI得到連接工廠的引用,ctx是上下文Context類型的對象
 2 QueueConnectionFactory qConnFact=(QueueConnectionFactory)ctx.lookup("cfJndi");
 3 //使用連接工廠創建一個連接
 4 QueueConnection qConn=qConnFact.createQueueConnection();
 5 //使用連接創建一個會話
 6 QueueSession qSess=qConn.createQueueSession(false,javax.jms.QueueSession. AUTO_ACKNOWLEDGE);
 7 //使用上下文和JNDI得到消息隊列的引用
 8 Queue q=(Queue)ctx.lookup("myQueue");
 9 //使用連接創建一個需要發送的消息類型的實例
10 QueueSender qSend=qSess.createSender(q);
11 System.out.println("開始發送消息......");

 

 (3) JDBC中的工廠方法:

1 Connection conn=DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433; DatabaseName=DB;user=sa;password=");
2 Statement statement=conn.createStatement();
3 ResultSet rs=statement.executeQuery("select * from UserInfo");

 

模式擴展

    使用多個工廠方法:在抽象工廠角色中可以定義多個工廠方法,從而使具體工廠角色實現這些不同的工廠方法,這些方法可以包含不同的業務邏輯,以滿足對不同的產品對象的需求。
    產品對象的重復使用:工廠對象將已經創建過的產品保存到一個集合(如數組、List等)中,然后根據客戶對產品的請求,對集合進行查詢。如果有滿足要求的產品對象,就直接將該產品返回客戶端;如果集合中沒有這樣的產品對象,那么就創建一個新的滿足要求的產品對象,然后將這個對象在增加到集合中,再返回給客戶端。
    多態性的喪失和模式的退化:如果工廠僅僅返回一個具體產品對象,便違背了工廠方法的用意,發生退化,此時就不再是工廠方法模式了。一般來說,工廠對象應當有一個抽象的父類型,如果工廠等級結構中只有一個具體工廠類的話,抽象工廠就可以省略,也將發生了退化。當只有一個具體工廠,在具體工廠中可以創建所有的產品對象,並且工廠方法設計為靜態方法時,工廠方法模式就退化成簡單工廠模式。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM