goo我們先來看看這兩篇文章:
設計模式之Factory
板橋里人 http://www.jdon.com 2002/10/07(轉載請保留)
工廠模式定義:提供創建對象的接口.
為何使用?
工廠模式是我們最常用的模式了,著名的Jive論壇 ,就大量使用了工廠模式,工廠模式在Java程序系統可以說是隨處可見。
為什么工廠模式是如此常用?因為工廠模式就相當于創建實例對象的new,我們經常要根據類Class生成實例對象,如A a=new A() 工廠模式也是用來創建實例對象的,所以以后new時就要多個心眼,是否可以考慮實用工廠模式,雖然這樣做,可能多做一些工作,但會給你系統帶來更大的可擴展性和盡量少的修改量。
我們以類Sample為例, 如果我們要創建Sample的實例對象:
Sample sample=new Sample();
可是,實際情況是,通常我們都要在創建sample實例時做點初始化的工作,比如賦值 查詢數據庫等。
首先,我們想到的是,可以使用Sample的構造函數,這樣生成實例就寫成:
Sample sample=new Sample(參數);
但是,如果創建sample實例時所做的初始化工作不是象賦值這樣簡單的事,可能是很長一段代碼,如果也寫入構造函數中,那你的代碼很難看了(就需要Refactor重整)。
為什么說代碼很難看,初學者可能沒有這種感覺,我們分析如下,初始化工作如果是很長一段代碼,說明要做的工作很多,將很多工作裝入一個方法中,相當于將很多雞蛋放在一個籃子里,是很危險的,這也是有背于Java面向對象的原則,面向對象的封裝(Encapsulation)和分派(Delegation)告訴我們,盡量將長的代碼分派“切割”成每段,將每段再“封裝”起來(減少段和段之間偶合聯系性),這樣,就會將風險分散,以后如果需要修改,只要更改每段,不會再發生牽一動百的事情。
在本例中,首先,我們需要將創建實例的工作與使用實例的工作分開, 也就是說,讓創建實例所需要的大量初始化工作從Sample的構造函數中分離出去。
這時我們就需要Factory工廠模式來生成對象了,不能再用上面簡單new Sample(參數)。還有,如果Sample有個繼承如MySample, 按照面向接口編程,我們需要將Sample抽象成一個接口.現在Sample是接口,有兩個子類MySample 和HisSample .我們要實例化他們時,如下:
Sample mysample=new MySample();
Sample hissample=new HisSample();
隨著項目的深入,Sample可能還會"生出很多兒子出來", 那么我們要對這些兒子一個個實例化,更糟糕的是,可能還要對以前的代碼進行修改:加入后來生出兒子的實例.這在傳統程序中是無法避免的.
但如果你一開始就有意識使用了工廠模式,這些麻煩就沒有了.
工廠方法
你會建立一個專門生產Sample實例的工廠:
public class Factory{
public static Sample creator(int which){
//getClass 產生Sample 一般可使用動態類裝載裝入類。
if (which==1)
return new SampleA();
else if (which==2)
return new SampleB();
}
}
|
那么在你的程序中,如果要實例化Sample時.就使用
Sample sampleA=Factory.creator(1);
這樣,在整個就不涉及到Sample的具體子類,達到封裝效果,也就減少錯誤修改的機會,這個原理可以用很通俗的話來比喻:就是具體事情做得越多,越容易范錯誤.這每個做過具體工作的人都深有體會,相反,官做得越高,說出的話越抽象越籠統,范錯誤可能性就越少.好象我們從編程序中也能悟出人生道理?呵呵.
使用工廠方法 要注意幾個角色,首先你要定義產品接口,如上面的Sample,產品接口下有Sample接口的實現類,如SampleA,其次要有一個factory類,用來生成產品Sample,如下圖,最右邊是生產的對象Sample:

進一步稍微復雜一點,就是在工廠類上進行拓展,工廠類也有繼承它的實現類concreteFactory了。
抽象工廠
工廠模式中有: 工廠方法(Factory Method) 抽象工廠(Abstract Factory).
這兩個模式區別在于需要創建對象的復雜程度上。如果我們創建對象的方法變得復雜了,如上面工廠方法中是創建一個對象Sample,如果我們還有新的產品接口Sample2.
這里假設:Sample有兩個concrete類SampleA和SamleB,而Sample2也有兩個concrete類Sample2A和SampleB2
那么,我們就將上例中Factory變成抽象類,將共同部分封裝在抽象類中,不同部分使用子類實現,下面就是將上例中的Factory拓展成抽象工廠:
public abstract class Factory{
public abstract Sample creator();
public abstract Sample2 creator(String name);
}
public class SimpleFactory extends Factory{
public Sample creator(){
.........
return new SampleA
}
public Sample2 creator(String name){
.........
return new Sample2A
}
}
public class BombFactory extends Factory{
public Sample creator(){
......
return new SampleB
}
public Sample2 creator(String name){
......
return new Sample2B
}
}
|
從上面看到兩個工廠各自生產出一套Sample和Sample2,也許你會疑問,為什么我不可以使用兩個工廠方法來分別生產Sample和Sample2?
抽象工廠還有另外一個關鍵要點,是因為 SimpleFactory內,生產Sample和生產Sample2的方法之間有一定聯系,所以才要將這兩個方法捆綁在一個類中,這個工廠類有其本身特征,也許制造過程是統一的,比如:制造工藝比較簡單,所以名稱叫SimpleFactory。
在實際應用中,工廠方法用得比較多一些,而且是和動態類裝入器組合在一起應用,
舉例
我們以Jive的ForumFactory為例,這個例子在前面的Singleton模式中我們討論過,現在再討論其工廠模式:
public abstract class ForumFactory {
private static Object initLock = new Object();
private static String className = "com.jivesoftware.forum.database.DbForumFactory";
private static ForumFactory factory = null;
public static ForumFactory getInstance(Authorization authorization) {
//If no valid authorization passed in, return null.
if (authorization == null) {
return null;
}
//以下使用了Singleton 單態模式
if (factory == null) {
synchronized(initLock) {
if (factory == null) {
......
try {
//動態轉載類
Class c = Class.forName(className);
factory = (ForumFactory)c.newInstance();
}
catch (Exception e) {
return null;
}
}
}
}
//Now, 返回 proxy.用來限制授權對forum的訪問
return new ForumFactoryProxy(authorization, factory,
factory.getPermissions(authorization));
}
//真正創建forum的方法由繼承forumfactory的子類去完成.
public abstract Forum createForum(String name, String description)
throws UnauthorizedException, ForumAlreadyExistsException;
....
}
|
因為現在的Jive是通過數據庫系統存放論壇帖子等內容數據,如果希望更改為通過文件系統實現,這個工廠方法ForumFactory就提供了提供動態接口:
private static String className = "com.jivesoftware.forum.database.DbForumFactory";
你可以使用自己開發的創建forum的方法代替com.jivesoftware.forum.database.DbForumFactory就可以.
在上面的一段代碼中一共用了三種模式,除了工廠模式外,還有Singleton單態模式,以及proxy模式,proxy模式主要用來授權用戶對forum的訪問,因為訪問forum有兩種人:一個是注冊用戶 一個是游客guest,那么那么相應的權限就不一樣,而且這個權限是貫穿整個系統的,因此建立一個proxy,類似網關的概念,可以很好的達到這個效果.
看看Java寵物店中的CatalogDAOFactory:
public class CatalogDAOFactory {
/**
* 本方法制定一個特別的子類來實現DAO模式。
* 具體子類定義是在J2EE的部署描述器中。
*/
public static CatalogDAO getDAO() throws CatalogDAOSysException {
CatalogDAO catDao = null;
try {
InitialContext ic = new InitialContext();
//動態裝入CATALOG_DAO_CLASS
//可以定義自己的CATALOG_DAO_CLASS,從而在無需變更太多代碼
//的前提下,完成系統的巨大變更。
String className =(String) ic.lookup(JNDINames.CATALOG_DAO_CLASS);
catDao = (CatalogDAO) Class.forName(className).newInstance();
} catch (NamingException ne) {
throw new CatalogDAOSysException("
CatalogDAOFactory.getDAO: NamingException while
getting DAO type : \n" + ne.getMessage());
} catch (Exception se) {
throw new CatalogDAOSysException("
CatalogDAOFactory.getDAO: Exception while getting
DAO type : \n" + se.getMessage());
}
return catDao;
}
}
|
CatalogDAOFactory是典型的工廠方法,catDao是通過動態類裝入器className獲得CatalogDAOFactory具體實現子類,這個實現子類在Java寵物店是用來操作catalog數據庫,用戶可以根據數據庫的類型不同,定制自己的具體實現子類,將自己的子類名給與CATALOG_DAO_CLASS變量就可以。
由此可見,工廠方法確實為系統結構提供了非常靈活強大的動態擴展機制,只要我們更換一下具體的工廠方法,系統其他地方無需一點變換,就有可能將系統功能進行改頭換面的變化。
設計模式如何在具體項目中應用見《Java實用系統開發指南》
--------------------------------------------------------------------------------------------------------------------------------------------------------------
隨著模式概念的普及,了解模式和使用模式的程序員越來越多,很多人在學習模式的時候,都會有這樣一種疑惑:“有必要搞得這么復雜嗎?”。的確,因為教程的例子過于簡單化(這樣方便讀者學習),或者是作者選例子的時候并沒有很好體現所講模式的優點,很多情況下如果僅就其例子的問題來說,用模式是太復雜了。因此才導致這樣的誤解:“模式就是把簡單的問題復雜化嗎?”。當然不是,隨著你開發實踐的不斷豐富,你終會發現模式強大威力,而且模式也并非貴族化的編程方式,它就是一些經過提煉了的解決問題的方法技巧。
通過學習模式,程序員開始告別過去準直線式的代碼方式,模式開擴了我們的視野,強化了我們面向對象編程的思維方式。然而現在又出現了另一個普遍的問題,盲目應用模式。模式是問題的解決方案,先有問題才有模式,模式是依附于所要解決的問題的而生的。必須了解模式在很多情況下是以提高代碼的復雜度為代價來增強靈活性、可復用性。如果在自已的代碼中使用某一模式僅只提高了代碼的復雜度,而其它方面收效甚微,或者某部份代碼根本就不存在靈活性及高復用性的需求,那么我們就沒有必要為使用模式而放棄更直觀簡單的代碼寫法。
一流的高手90%精力關注問題的解決方案,因為找到了好的解決方案,再寫起代碼會很輕松代碼也簡潔流暢,看這樣的代碼是一種享受和提高;二流的熟手90%精力關注代碼實現,因為問題的解決方案并非最佳,實現的代碼也會比較復雜;三流菜鳥記流水帳,90%精力在敲鍵盤,常常做了大半才發現行不通,回過頭來再用90%的時間敲鍵盤,根本不會用到任何模式,寫出來的代碼的只有他自已才能看懂。做出來的軟件也是支離破碎,做一丁點改動都要大費周折,而且你還不知道改動后會產生什么問題,大有住危房里的感覺。
在這里還是舉一個濫用模式的例子吧。我曾參與過一個大集團公司OA系統的第二期開發,開發沿用原有代碼架構并增加新的功能模塊。文檔很少我讀原代碼時就被它程序里的代碼轉來轉去搞得頭大如斗,最后讀懂了:原代碼架構總體采用工廠模式,而且是最復雜的抽象工廠模式。它把所有模塊類都通過工廠生成還工廠套工廠,并且每一個模塊類都有一個接口,每個接口也只有一個模塊現實類,因為涉及權限控制還用了代理(proxy)模式。 讀懂代碼后我開始嵌入代碼,發現每新增一個類,都要到六個Java文件中去增加相應代碼,而在類中每增加一個方法,也要到它的接口等四個Java文件中去增加相應代碼。天呀!!!記得當時我的小姆指常會不聽使喚,就是因為頻繁的使用Ctrl+C 、Ctrl+V,小姆指按著Ctrl鍵給累的。整個項目組苦不堪言,真煩透了。項目結束后我回顧發現:代理模式用得還對(現在針對權限這類橫向控制有AOP編程這種新的解決辦法了)但工廠模式在這里根本就是畫蛇添足,不僅沒有解決什么問題,反而增加代碼復雜度和耦合性,降低了開發效率連維護難度都提高了。而且那種每個類簡單的加一個接口的方式,更是沒有道理,這讓我很想說周星馳說過的一句話:“球~~~不是這么踢~~~~的,接口~~~不是這么用~~~的”。言歸正傳,我們先來看這樣一個常見問題:某系統需要支持多種類型的數據庫。用過Oracle、MSSQL等數據庫的人都知道,它們的SQL編寫方式都各有些不同。比如說Oracle的唯一標識自動+1字段用的是序列,MSSQL改一下字段屬性就成了,還有各種各自特有的SQL用法。為了支持多數據庫,難道我們要開發多套系統?當然NO。請看下面的解決方案。
即然數據庫存在多種,我們可以將系統中所有對數據庫的操作抽象出來,寫成一個個方法組合到一個類中,有幾種數據庫我們就寫幾個這樣的類。具體設計類圖如下:
簡要說明:
OracleDataOperate、SqlserverDataOperate、MysqlDataOperate,分別代表Oracle、Sqlserver、Mysql這三種數據庫的操作類。繼承自AbstractDataOperate
AbstractDataOperate是一個抽象類,包含了那些不同種類數據庫都是一樣代碼的操作方法。繼承自DataOperate
DataOperate是上面說的數據操作類的統一接口,只有兩個方法:取得一條記錄、插入一條記錄。
DataOperateFactory是一個工廠方法,統一用它的方法來得到數據庫操作類的實例。
SampleClass是我們系統的某個功能模塊的類。
People是一個實體類,代表一條記錄。三個字段 oid唯一標識符、name姓名、date生日。
詳細說明:
1、所有系統功能模塊類只認DataOperat這個接口還不必管具體的實現類是OracleDataOperate還SqlserverDataOperate。DataOperate源代碼如下:
public interface DataOperate {
//根據記錄的唯一標識取出一條記錄
People getPeople(String oid);
//插入一條記錄
boolean insertPeople(People people);
}
2、AbstractDataOperate、OracleDataOperate、SqlserverDataOperate、MysqlDataOperate都是繼承DataOperate接口的,沒什么好說的,省略。
3、DataOperateFactory。我們看看工廠方法怎么寫的。
public class DataOperateFactory {
public static final int ORACLE = 0; //定義三個表示數據庫類型的常量
public static final int MYSQL = 1;
public static final int SQLSERVER = 2;
private static DataOperate db;
private static int dataType = MYSQL;
/**
* 根據數據庫類型(dataType)取得一個數據庫操作類的實例,
* 這里對DataOperate使用了單例模式,因為OracelDataOperate等都是無狀態的工具類,
* 所以整個系統只保留一個實例就行了。
*
* @return 返回的是接口,客戶端不必關心具體是用那個實現類
*/
public static DataOperate getInstance() {
if (db == null) {
if (dataType == ORACLE) //根據dateType返回相應的實現類
return new OracelDataOperate();
if (dataType == MYSQL)
return new MysqlDataOperate();
if (dataType == SQLSERVER)
return new SqlserverDataOperate();
}
return db;
}
}
4、接下來就看看使用端是如何調用工廠方法和使用數據操作類的。
/**
* 系統某個功能類
*/
public class SampleClass {
private DataOperate db; //聲明一個數據庫操作類,注意這里用的是接口噢
/**某方法*/
public void sampleMethod() {
db = DataOperateFactory.getInstance();//得到單一實例
People p = db.getPeople("123"); //取得一條記錄
db.insertPeople(p);//再插回去
}
}
我們發現SampleClass中根本沒有出現OracelDataOperate、MysqlDataOperate等的影子,這就是接口的威力。客戶端不必針對OracelDataOperate等寫不同的代碼,它只關心DataOperate即可,具體要取那個類的邏輯就由DataOperateFactory負責了。
總結:
從例子中我們可以看到什么是面向接口的編程方式。SampleClass使用數據操作類可以不必關心具體是那個類,只要是符合接口的都行
要實例?只須調用DataOperateFactory.getInstance()即可,其它的交于DataOperateFactory這個工廠來做吧,使用端什么都不用關心。
我們要支持新的數據庫類型,只須要象OracelDataOperate那樣,再寫一個繼承AbstractDataOperate的類即可,比如SysbaseDataOperate。然后到DataOperateFactory中加入相應代碼即可。
如果我們想要可配置性更高,可以用private static int dataType = MYSQL;中的值設置到一個文本文件中。
對于開發支持多種數據庫的系統,強烈建議使用hibernate,我現在做的系統就是用hibernate的,開發時用Mysql,到要給客戶時將數據庫換了DB2,程序不用做任何改動,真正的無逢移植。不過這樣,本文所提到的方法就沒什么用了.
這兩篇是我找到的關于Factory Mode的文章,但是前面的論述很好,后面的例子卻不怎么樣,特別是第二篇的例子并不符合實際也沒必要這樣做,純粹是接口也可以實現,Factory Mode并沒有體現多少。第一篇的就似乎比較切實,但是說的也不夠清晰。