前言 首先是例行的國際慣例,本文寫於本人學習設計模式的路上,適合同樣學習設計模式的朋友交流使用,大神誤入請留下您寶貴的意見,先行謝過; 前面我們已經學習過 簡單工廠模式 ,今天來學習第二個工廠模式 方法工廠模式 。 定義 百度百科 工廠方法模式的意義是定義一個創建產品對象的工廠介面,將實際創建工作推 ...
前言
首先是例行的國際慣例,本文寫於本人學習設計模式的路上,適合同樣學習設計模式的朋友交流使用,大神誤入請留下您寶貴的意見,先行謝過;
前面我們已經學習過簡單工廠模式,今天來學習第二個工廠模式方法工廠模式。
定義
百度百科
工廠方法模式的意義是定義一個創建產品對象的工廠介面,將實際創建工作推遲到子類當中,核心工作類不再負責對象的創建,這樣核心類成為一個抽象工廠角色,僅負責具體工廠子類必須實現的介面,這樣進一步抽象化的好處是使得工廠方法模式可以使系統在不修改具體工廠角色的情況下引入新的產品。
個人拙見
前面我們已經學習了簡單工廠模式或者說是靜態工廠模式,在簡單工廠模式中,每添加一個產品子類的同時還需要去修改核心的工廠類,添加對應的判斷語句,這是對擴展開放的同時也對修改開放,違反了設計原則中的開閉原則,但是今天的工廠方法原則則正好彌補了這一缺陷,將核心工廠抽象為一個抽象介面,核心工廠不再負責具體產品的創建,僅僅負責具體工廠子類必須實現的介面,此外給每一類產品都設置一個工廠類,通過工廠類生成對應的產品,以此來達到遵循開閉原則的目的,其次,產品和工廠之間使用抽象的方式進行關聯,同樣遵循了依賴倒置原則,另外,單一職責原則自不必說,工廠方法還很好的遵循了迪米特法則,也就是最少知道法則。
當然,方法工廠模式肯定是針對複雜對象的創建時所使用的,如果一個類僅僅通過new就可以創建,當然就沒必要使用工廠方法模式了,不然就會增加不必要的工廠類,因為系統臃腫不利於後期維護。
類圖
上圖中,左半部分是產品介面以及具體的產品類,右半部分是工廠介面以及對應的工廠類,每一個工廠對應一種產品,這種方式就避過了簡單工廠模式中的判斷語句,如果要添加新的產品,那麼只需要讓產品類實現產品介面,對應的工廠類實現核心工廠介面即可,不必去修改原來的代碼,滿足開閉原則。
代碼實現
下麵就用JAVA代碼簡單的實現以下上述的類圖,首先需要一個產品介面:
public interface IProduct {
public void productMethod();
}
然後是兩種具體的產品:
public class ProductA implements IProduct{
@Override
public void productMethod() {
System.out.println("產品A方法");
}
}
public class ProductB implements IProduct{
@Override
public void productMethod() {
System.out.println("產品B方法");
}
}
然後是抽象工廠介面:
public interface IFactory {
public IProduct createProduct();
}
產品A對應的工廠:
public class ProductFactoryA implements IFactory{
@Override
public IProduct createProduct() {
return new ProductA();
}
}
產品B對應的工廠:
public class ProductFacctoryB implements IFactory{
@Override
public IProduct createProduct() {
return new ProductB();
}
}
測試類:
public class Test {
public static void main(String[] args) {
IFactory factory = new ProductFactoryA();
IProduct productA = factory.createProduct();
productA.productMethod();//產品A方法
factory = new ProductFacctoryB();
IProduct productB = factory.createProduct();
productB.productMethod();//產品B方法
}
}
通過上述的測試代碼,我們可以看到,在客戶端我們可以隨意的切換具體的產品工廠和具體產品,並且不需要修改任何代碼,如果需要添加新的產品,也只是需要添加具體的產品類和對應的工廠類即可,然後就可以直接在客戶端調用,並不需要修改任何原來的代碼,這也正好滿足了對擴展開放對修改關閉的開閉原則,同時也去除掉了簡單工廠中的ifelse的多重判斷語句。
使用場景
該部分內容轉自博客園左瀟龍博客
應用場景一:JDBC連接
使用JDBC連接資料庫時,如果資料庫是mysql,那麼就需要使用mysql的驅動,如果是Oracle資料庫,那麼就需要使用Oracle的驅動,但是我們在寫JDBC程式的時候大部分是一樣的,不一樣的只是一樣那就是驅動包不同(前提是SQL是標準的SQL),雖然我們提供給JDBC程式的驅動包不同,但是返回的Connection卻是相同的,這是怎麼做到的呢?其實這裡使用的就是工廠方法模式,具體的流程如下:
首先,JDBC API提供了整個資料庫操作的框架,具體的資料庫的操作細節由各個資料庫提供商自己實現,而我們要操作的僅僅是JDBC API,而不是具體的資料庫細節,這就用到了java.sql包中的Driver和Connection介面,這兩個介面的部分代碼如下所示:
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;
}
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;
}
在Driver介面中,註釋中給出了所有的驅動類都必須實現這個介面,也就是說所有的驅動類都必須實現Driver介面,否則沒法使用,然後在Driver介面中還有一個connect()方法,返回一個Connnection用以操作資料庫,但是Connection也是一個介面,只給出了約束,因此各個資料庫提供商在提供驅動類的同時還需要給出Connection的實現類,用來具體的操作資料庫的crud等操作,因此不同的資料庫就需要提供不同的驅動類和連接實現類,如mysql要提供com.jdbc.mysql.Driver以及mysql的ConnectionImpl,Oracle要提供oracle.jdbc.driver.OracleDriver以及Oracle的ConnectionImpl類。
說到這裡有人就奇怪了,說我平時寫JDBC的時候也沒看到Driver這個介面啊,我就用到了DriverManager類,這是因為在載入資料庫驅動的時候,在Driver類中自動的將驅動類註冊給了DriverManager,這時候就可以使用DriverManager來獲取到資料庫連接了,因此可能會看不到Driver介面的存在,但是它確確實實是存在的,下麵可以看一下mysql的Driverr類的代碼:
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!");
}
}
}
上述幾個類的類圖如下(請原諒我又盜用了左大神的類圖):
在使用JDBC連接的時候,我們不需要知道mysql或者oracle是怎麼樣提供的資料庫操作的,我們只需要根據JDBC API中的Connection和DriverManager拿到資料庫連接,處理具體的資料庫業務即可,這個場景也是工廠方法模式的一個優點:屏蔽具體的產品類,也就是說具體的產品類如何變化調用者並不關心,客戶端只需要關心產品的介面,只要產品的介面不發生變化,那麼不管下層模塊如何變化都不會影響上層模塊的調用,這是遵循依賴倒置原則的具體表現。
應用場景二:
使用工廠方法模式並不是說只能使用面向介面,有時候也需要關心具體的產品實現類,因為每一類產品可能都有不同的地方,例如HashSet和ArrayList,這兩個類的具體實現也使用到了工廠方法模式,具體的類圖如下(我保證這是最後一次盜圖了,以後再也不敢了):
這時候一看,我擦,上面不是說要面向介面編程嘛,那我以後就使用Iterable和Iterator這倆了,這個,如果可以的話,類庫的設計者何必要設計什麼ArrayList、TreeSet這麼多集合類呢,這裡的每個具體的產品類都有其特性,因此這裡就不能單單的使用介面而是要使用其對應的產品類。
上述的兩個場景一個是面向介面的,一個是面向具體的產品的,這也是工廠方法模式提供的兩種使用情況:如果各個產品提供的功能類似,如mysql或者Oracle都是提供資料庫連接,那麼就可以使用面向介面的方式來處理,但是如果是針對於具體的產品,此時調用者清楚的知道應該使用哪個具體工廠服務,實例化該具體工廠,生產出具體的產品來,這就需要考慮倒具體的產品了,不能單單的是面向介面了。
優點
- 代碼結構清晰,良好的封裝性。將產品的創建過程封裝在對應的工廠中,調用者不需要知道對象的創建過程,減少了模塊之間的耦合性;
- 良好的擴展性。在需要添加新的產品進來時,只需要添加符合要求的工廠類和產品類即可,不需要修改原有代碼,遵循開閉原則;
- 屏蔽具體的產品。如JDBCl連接,我們只需要根據API調用Connection和DriverManeger即可,不需要知道mysql或者oracle是如何實現底層的資料庫操作的;
- 良好的解耦合能力。上層模塊只需要知道抽象工廠即可,不需要知道具體的實現類,符合迪米特法則。
與簡單工廠模式的區別聯繫
首先,工廠方法模式是簡單工廠模式的擴展,給每一個產品都添加一個對應的工廠,使用抽象工廠作為核心工廠,核心工廠不再關註具體產品的創建而是交由它的子類來創建,但是在簡單工廠模式中僅僅只存在一個工廠,並且是核心工廠;
其次,工廠方法模式中避免了在核心工廠中使用多重判斷語句的詬病,並且滿足對於修改關閉對於擴展開放的開閉原則;
第三,如果一個模塊中僅僅存在一個工廠就可以滿足條件,那麼完全沒有必要為每個產品都產生對應的工廠,雖然簡單工廠模式不滿足開閉原則,但是不能否認簡單工廠模式也是很強大的;
總結
工廠方法模式是一個創建型的設計模式,遵循了單一職責、依賴倒置以及開閉原則,有兩種使用場景,一種是客戶端不需要知道具體的產品是如何實現的,只需要面向介面編程即可,另一種是客戶端完全清楚每種的產品的實現和用途,客戶端需要使用具體的產品了。
參考文章
- http://www.cnblogs.com/zuoxiaolong/p/pattern5.html
- http://www.cnblogs.com/cbf4life/archive/2009/12/20/1628494.html
- http://blog.csdn.net/zhengzhb/article/details/7348707