標籤:oracle 閱讀 簡單原廠模式 csdn 簡單 服務 轉型 led http
轉載: http://www.cnblogs.com/zuoxiaolong/p/pattern5.html
本章我們繼續討論新的設計模式,工廠方式模式,在這之前,LZ決定先給出引自其它地方的標準定義以及類圖。
定義:Factory 方法(Factory Method)模式的意義是定義一個建立產品對象的工廠介面,將實際建立工作延遲到子類當中。核心工廠類不再負責產品的建立,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的介面,這樣進一步抽象化的好處是使得Factory 方法模式可以使系統在不修改具體工廠角色的情況下引進新的產品。
可以看到Factory 方法模式中定義了一個工廠介面,而具體的建立工作延遲到具體的工廠類,它是對簡單原廠模式中的工廠類進一步抽象化,從而產生一個工廠類的抽象和實現體系,從而彌補簡單原廠模式對修改開放的詬病。
下面LZ給出Factory 方法模式的類圖,該類圖和定義引自百度百科。
可以看到,上面右半部分是產品抽象和實現體系,左半部分是工廠抽象和實現體系,其中工廠體系依賴於產品體系,每一個工廠負責創造一種產品,這就省去了簡單工廠中的elseif判斷,又用戶端決定執行個體化一個特定的工廠去建立相應的產品。
下面LZ簡單的使用JAVA代碼詮釋上述標準的Factory 方法模式的類圖。
首先是抽象產品介面。
public interface Light { public void turnOn(); public void turnOff(); }
下面是具體的產品。
public class BuldLight implements Light{ public void turnOn() { System.out.println("BuldLight On"); } public void turnOff() { System.out.println("BuldLight Off"); }}
public class TubeLight implements Light{ public void turnOn() { System.out.println("TubeLight On"); } public void turnOff() { System.out.println("TubeLight Off"); }}
下面是抽象的工廠介面。
public interface Creator { public Light createLight();}
下面是建立指定產品的具體工廠。
public class BuldCreator implements Creator{ public Light createLight() { return new BuldLight(); }}
public class TubeCreator implements Creator{ public Light createLight() { return new TubeLight(); }}
下面我們寫個測試類別去實驗一下這個Factory 方法模式的執行個體代碼。
public class Client { public static void main(String[] args) { Creator creator = new BuldCreator(); Light light = creator.createLight(); light.turnOn(); light.turnOff(); creator = new TubeCreator(); light = creator.createLight(); light.turnOn(); light.turnOff(); }}
運行結果如下。
可以看到,我們使用可以隨意的在具體的工廠和產品之間切換,並且不需要修改任何代碼,就可以讓原來的程式正常運行,這也是Factory 方法模式對擴充開放的表現,另外Factory 方法模式彌補了簡單原廠模式不滿足開閉原則的詬病,當我們需要增加產品時,只需要增加相應的產品和工廠類,而不需要修改現有的代碼。
上面的樣本可以比較清楚的展示各個類之間的關係,但是始終缺乏說服力,因為它完全沒有什麼實際意義,下面LZ就給出一些我們接觸過的例子來說明Factory 方法模式的好處。
關於能夠說明Factory 方法模式的執行個體,LZ翻遍了所有能找到的源碼,想尋找一個讓各位讀者既能學習到新的東西,又能對Factory 方法理解更深的現有的優秀架構的設計。經過跋山涉水,LZ決定還是拿資料庫連接來說事,我知道你想說,我去,又是資料庫連接。LZ只想說,我們每天做的最多的就是增刪改查好嗎,其它的咱也不認識啊,囧。
眾所周知,為了統一各個資料庫操作的標準,於是有了JDBC的API,它用於給我們這種被稱作只會使用現成的東西的程式猿,提供一系列統一的,標準化的操作資料庫的介面。其實JDBC的各個類或介面,就是我們操作資料庫的過程中各個協助者的抽象,這樣的設計是為了讓我們對資料庫的操作依賴於抽象,還記得我們在設計模式總綱中提到的一句話嗎,用抽象構建架構,用細節擴充實現。
JDBC API(即抽象的介面或類)就是整個資料庫操作的架構,而各個資料庫的驅動就是那些細節。而我們的操作依賴於JDBC API,而不是任何一個具體資料庫的細節。
JDBC是如何統一了資料庫世界的呢?其實最主要的就是靠兩個介面,就統一了世界。。。
來看第一個介面Driver,附上源碼。
package java.sql;import java.sql.DriverPropertyInfo;import java.sql.SQLException;/** * The interface that every driver class must implement. */public interface Driver { Connection connect(String url, java.util.Properties info) throws SQLException; boolean acceptsURL(String url) throws SQLException; DriverPropertyInfo[] getPropertyInfo(String url, java.util.Properties info) throws SQLException; int getMajorVersion(); int getMinorVersion(); boolean jdbcCompliant();}
由於篇幅,LZ刪掉了很多注釋,只保留了這個類注釋的第一句話,翻譯過來是這是一個任何驅動類都必須實現的介面。多麼霸氣啊。也就是每個資料庫廠商都必須實現這個介面來提供JDBC服務,即java資料庫連接服務,來方便程式猿對資料庫應用編程。
我們先忽略掉下面的五個方法,第一個方法毫無疑問是這個介面中相對而講最重要的方法了,即創造一個資料庫連接,雖然方法名稱是connect,但是我覺得這個方法完全可以改為createConnection。
提到Connction,這個介面我們一定不陌生,它的源碼也已經在代理模式一章出現過,這裡我們再次讓它出場,我依舊會刪掉它的大部分方法,限於篇幅。
package java.sql;import java.sql.PreparedStatement;import java.sql.SQLException;/** * <P>A connection (session) with a specific * database. SQL statements are executed and results are returned * within the context of a connection. * <P> */public interface Connection extends Wrapper { Statement createStatement() throws SQLException; PreparedStatement prepareStatement(String sql) throws SQLException;}
以上便是Connection介面,這裡只留下了兩個方法,這兩個方法相信各位讀者都非常熟悉,它們都是我們最經常用的方法之二。
以上兩個介面作為JDBC API的一部分,它們相當於告訴了資料庫生產廠商兩個要求。
第一,資料庫廠商要提供一個資料庫驅動類,它的作用可以是可以創造資料庫連接,而這個資料庫連接向上轉型為我們JDBC的Connection。
第二,資料庫廠商要提供一個資料庫連接的實作類別,這個實作類別可以執行具體資料庫的各個操作,比如幫我們執行SQL,返回執行結果,關閉串連等等。
我們都知道mysql的驅動類位於com.mysql.jdbc.Driver,而mysql的connection實作類別也在這個包中,名稱是ConnectionImpl,而相應的oracle也有驅動類,位於oracle.jdbc.driver.OracleDriver,相應的oracle也有connection實作類別,位於oracle.jdbc.OracleConnectionWrapper。一般每個資料庫都會有一個Connection的擴充介面,這個介面的作用是提供使用者針對當前資料庫特殊的操作。
這裡我們忽略掉這些中間介面以及抽象類別,我給出上述六個類的UML圖,如果各位以前知道Factory 方法模式的話,各位看一下,它們的關係是否很熟悉。
我們對比上面標準的Factory 方法模式,就會發現它們的關係不正是Factory 方法模式嗎?
Factory 方法模式就是提供一個抽象的工廠,一個抽象的產品,在上述當中相當於Driver(資料庫連接工廠)和Connection(抽象產品),實現的一方需要提供一個具體的工廠類(比如mysql驅動)和一個具體的產品(比如mysql資料庫連接)。
用戶端調用時不依賴於具體工廠和產品(即到底是mysql驅動,mysql資料庫連接還是oracle驅動,oracle串連,我們程式猿不需要管的,我們只管使用抽象的driver和connection,對吧?),而是依賴於抽象工廠和抽象產品完成工作。
各位可以看到我在類圖裡面加入了一個DriverManager,這個類相信各位也不陌生,這是我們天天打交道的類,雖說因為hibernate和ibatis的封裝,或許我們不能經常看到,但LZ相信它活在每個程式猿的心中。
DriverMananger在這個設計當中扮演者一個管理者的角色,它幫我們管理資料庫驅動,讓我們不需要直接接觸驅動介面,我們擷取串連只需要和DriverManager打交道就可以,也就是說用戶端依賴於DriverManager和Connection就可以完成工作,不再需要與Driver關聯,所以上述說我們依賴於Driver和Connection,現在DriverManager幫我們管理Driver,那我們只需要依賴於DriverManager和Connection就可以了。
LZ在類圖中拉出了DriverManager的方法,其中的registerDriver方法正是我們註冊資料庫驅動的入口。來看看mysql的Driver中做了什麼,oracle類似。
public class Driver extends NonRegisteringDriver implements java.sql.Driver{ public Driver() throws SQLException { } static { try { DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can‘t register driver!"); } }}
可以看到,在類構造方法中,加入了registerDriver這個方法,所以當我們使用class.forName載入驅動的時候,將會把mysql驅動註冊到DriverManager,這時DriverManager中就會持有Mysql驅動所必要的資訊,我們就可以使用DriverManager來獲得具體的mysql串連了,當然,你要提供url,使用者名稱和密碼。
原來我們都是活在溫室裡的花朵,都被這些設計者細心呵護著,生怕我們知道一點底層的東西。記得LZ當初第一次看到Class.forName時,還覺得真是個神奇的東西,沒想到只是這些設計者給我們的糖外衣。
Factory 方法模式的好處和適用的情境都相對比較好理解。
好處就是,從類別關係上來說,它可以讓用戶端與具體的工廠與產品解耦,從業務角度來說,它讓用戶端與具體的產品解耦。
適用的情境就是我們需要一個產品幫我們完成一項任務,但是這個產品有可能有很多品牌(像這裡的mysql,oracle),為了保持我們對產品操作的一致性,我們就可能要用到Factory 方法模式。
Factory 方法模式也有它所不足的地方,可能你會說,這多好啊,我們操縱資料庫不再需要關心具體是哪個資料庫。是的,你很爽啊,那是因為這些產品的實現都不用你寫啊,都是資料庫廠商給你寫的。
假設產品數量巨多,而且需要我們親手去逐個實現的時候,Factory 方法模式就會增加系統的複雜性,到處都是工廠類和產品類,而且這裡所說的工廠類和產品類只是概念上的,真正的產品可能不是一兩個類就能搞定,否則mysql和oracle的驅動包為啥要那麼多類,而不是就一個Driver和一個Connection。
當然這也不是絕對,比如我們經常使用的HashSet和ArrayList,也是使用的Factory 方法模式,各位看下他們的類圖就看出來了。
各位可能會說,不對啊,這和我們剛才理解的不太一樣啊,按照剛才的說法,我們不是應該直接使用iterable和iterator嗎?這樣多牛X,我們不依賴於具體產品了。對於這個LZ表示三條黑線垂下,sun或者說oracle為了集合架構給你提供了這麼多具備各個特性的集合,你只用iterator和iterable,估計當初參與設計集合架構的人都要氣的去shi了。。
上述這便是Factory 方法模式另外一種用法了,剛才因為我們不關心真正的產品是什麼,所以我們直接使用抽象介面操作。但是我們使用iterable和iterator的時候,我們是關心真正產品的特性的,所以為了使用產品的特性,我們就需要使用產品特有的介面了,比如特殊的SortedSet可排序,比如ArrayList可以有重複元素,可以根據索引擷取元素等等。當然你依然是可以使用iterable和iterator的,但是不管你用什麼,在這種情境下,產品是你自己選的,一句話,你隨便。。。
兩種使用方式一種是對使用者透明的,一種是不透明的,一種是使用者對具體的產品不關心,這種情況下,一般產品提供的功能是類似的。一種是使用者非常瞭解產品的特性,並想使用產品的特性,這種情況下,一般產品只提供最基本的一致的功能,但每個產品都會有自己獨特的一面。
但是LZ個人覺得真正做項目的過程當中很少用到Factory 方法模式,這個模式更多的是協助我們理解現有的開源項目,就像現在,你是不是對JDBC的大體架構有了一定認識了呢,如果你不知道這個模式,可能看源碼會覺得一頭霧水呢。
另外,文章最後插播一段內容,如果各位看過上一章(簡單原廠模式)的話,一定還記得那個噁心的elseif結構,這是簡單工廠的詬病,它對擴充開放,對修改也開放。
簡單原廠模式在項目規模相對較小或者說具體的產品類相對不多的情況下(針對上章的描述,特指的servlet數量不多的情況下),其實這種設計還是可以接受的,因為少量的elseif可以換來我們開發上的便利。
所以LZ建議各位永遠不要忘記,規則只是用來指導你的,不是用來限制你的,只要設計合理,你的設計就是規則!
不過針對簡單原廠模式,你可以認為它給我們提供了一個思路,就是我們其實可以省掉那些讓人痛恨的xml配置,對於我們後續的最佳化有著一定指導意義。
就像上一章中的處理方式,很明顯存在著隱患,那就是在servlet數量急劇上升的時候,工廠類就會變得非常臃腫和複雜,變得難以維護和閱讀。本章LZ給各位讀者介紹一種最佳化方式,可以採取一項JDK當中在1.5版本引入的技術,即註解,去消除那些elseif的邏輯判斷。
我們可以參考struts2的做法,即每一個Servlet我們都可以採用註解去設定它的名稱,或者叫url,然後我們讓我們的簡單工廠依據這個去執行個體化我們的servlet。
根據以上方案,我們需要按照以下步驟讓我們的簡單工廠徹底死翹翹。
1.需要聲明一個註解,它可以用來給servlet標識它的名稱。
2.需要聲明一個註解的處理器,用來處理我們的註解,主要作用是通過一個CLASS檔案,去獲得它的註解資訊。
3.基於效能,我們需要將servlet與名稱的映射與應用的生命週期綁定,並且這份映射在整個應用當中有且僅有一份,且不可更改。
4.讓我們用於指派請求的過濾器,使用映射資訊將客戶請求對應到相應的servlet去處理,並且將指派邏輯移回過濾器,從而徹底刪除簡單工廠,即ServletFactory。
特別說一下,這四步當中,其中第三步是可選的,但也是必須的,因為如果不做這種處理,那麼你就等著你的項目N長時間開啟一個網頁吧。
以上是簡單工廠給我們的啟示,具體如何?這樣一個基於註解的請求分配的架構,LZ不再給各位一一示範,因為這已經只剩下一個堆積代碼的過程,具體的實現方案已經有了,如果各位讀者有興趣,可以私底下嘗試一下這種方式。
好了,Factory 方法模式就給各位分享到這吧,感謝各位的欣賞。
著作權聲明
zuoxiaolong(左瀟龍)
出處:部落格園左瀟龍的技術部落格--http://www.cnblogs.com/zuoxiaolong
您的支援是對博主最大的鼓勵,感謝您的認真閱讀。
本文著作權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文串連,否則保留追究法律責任的權利。
(四) Factory 方法模式