1. 程式人生 > >工廠方法模式(JAVA反射)

工廠方法模式(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等)中,然後根據客戶對產品的請求,對集合進行查詢。如果有滿足要求的產品物件,就直接將該產品返回客戶端;如果集合中沒有這樣的產品物件,那麼就建立一個新的滿足要求的產品物件,然後將這個物件在增加到集合中,再返回給客戶端。     多型性的喪失和模式的退化:如果工廠僅僅返回一個具體產品物件,便違背了工廠方法的用意,發生退化,此時就不再是工廠方法模式了。一般來說,工廠物件應當有一個抽象的父型別,如果工廠等級結構中只有一個具體工廠類的話,抽象工廠就可以省略,也將發生了退化。當只有一個具體工廠,在具體工廠中可以建立所有的產品物件,並且工廠方法設計為靜態方法時,工廠方法模式就退化成簡單工廠模式。