FACTORY 與 ABSTRACT FACTORY模式 —— 號令秦姬驅趙女,艷李秾桃臨戰場
junguo
這一次,將集中講一下創建型模式,主要以Factory和Abstract Factory模式為主。按上次的慣例,還是以例子開始。這次的例子仍以戰場和美女為例,呵呵,和戰場及美女死磕上了。采用這樣的例子,只是想幫助大家更好的記憶,我最簡化自己的例子,以幫助大家認識模式之形。寫完這個系列,也許會嘗試寫一些具體實例的模式。不過這樣的書其實也有,《道法自然》就是一本優秀的書,有興趣可以找來看看。還是來看我們要用到的例子:
恒王好武兼好色,遂教美女習騎射。
秾歌艷舞不成歡,列陣挽戈為自得。
眼前不見塵沙起,將軍倩影紅燈里。
叱咤時聞口舌香,霜矛雪劍嬌難舉。
丁香結子芙蓉絳,不系明珠系寶刀。
戰罷夜闌心力怯,脂痕粉漬污鮫鮹。
明年流寇走山東,強吞虎豹勢如蜂。
王率天兵思剿滅,一戰再戰不成功。
腥風吹折隴頭麥,日照旌旗虎帳空。
青山寂寂水澌澌,正是恒王戰死時。
雨淋白骨血染草,月冷黃沙鬼守尸。
紛紛將士只保身,青州眼見皆灰塵。
不期忠義明閨閣,憤起恒王得意人。
恒王得意數誰行,姽婳將軍林四娘。
號令秦姬驅趙女,艷李秾桃臨戰場。
秀鞍有淚春愁重,鐵甲無聲夜氣涼。
勝負自然難預定,誓盟生死報前王。
賊勢猖獗不可敵,柳折花殘實可傷。
魂依城郭家鄉近,馬踐胭脂骨髓香。
星馳時報入京師,誰家兒女不傷悲。
天子驚慌恨失守,此時文武皆垂首。
何事文武立朝綱,不及閨中林四娘。
我為四娘長嘆息,歌成余意尚彷徨。
這首詩取自《紅樓夢》第七十八回,該詩的起因如下。賈政和他的幕僚聊天的時候講到一個故事。當年有一個哥們被封為恒王,鎮守青州,此君好色成性,閑暇的時候還好弄武擺陣。自己玩的不過癮,把他選來的美女也拉了進來。每天看著這幫美女舞刀弄槍,還時常進行一些軍事演習。在眾多美女中有一個叫林四娘的,最為漂亮,而且武功最好,恒王讓她做了眾人的統領,并號為姽婳將軍。不想第二年,青州發生了農民起義,此君沒有把農民軍當回事,確把自己當回事。親自帶兵去征剿,結果打了兩戰,輸了兩戰,損兵折將,還將自己搭了進去,成了烈士。消息傳到青州城中,馬上亂了套。用指頭都可以想象得到,這樣的一個王,手下能有什么人才?眾多文武官員,不想著退敵護城,有的想著如何逃跑,有的想著不如獻城。這樣的情形激怒了林四娘,她集合起了所有女將,對眾將言到:“你我皆向蒙王恩,戴天履地,不能報其萬一。今王既殞身國事,我意亦當殞身于王。爾等有愿隨者,即時同我前往;有不愿者,亦早各散。”結果沒有一個人愿意離開,林四娘帶人連夜出城殺向賊營里面,剛開始人家沒防備,被她們殺了幾個人。后來一看是都是女人,就一起圍了上來,把眾女將殺的一個不剩。女為悅已者容變成了女為悅已者死,我在想為了這么一個王,她們死的值嗎?這幫農民起義軍也真能下的去手,不過想想好多事情都不可理喻。當年八路軍對日本鬼子采取了優待俘虜的政策。對自己人確毫不含糊,反拖肅反的時候,很多青年男女被秘密處決。為了節省子彈,還研發了很多土制方法,如直接把人填到井里(《白鹿原》里的共產黨員白玲就是這么死的),還有在人身上通上較弱的電流慢慢折磨至死(看過《綠色奇跡》沒有?看過的話,回想一下執行電刑時由于沒有往海綿中加水而被燒焦的哪個犯人的死狀,你就能想象到當時的慘烈程度了)。所以我記住了我們《馬哲》老師的一句話:千萬不要去沾什么主義。接著上面的故事,后來消息報了上去,朝廷重新派人來鎮壓農民起義,這次來人不象恒王那樣無能,剿滅了這幫農民起義軍。
眾幕僚聽著直流哈喇子,羨慕恒王羨慕的要死。文人的酸勁上來要做首詩來應和。賈政把寶玉,賈環和賈蘭也叫了過來,讓他們也一人做一首。寶玉寫下了這首古體長詩《姽婳詞》。《紅樓夢》是一本偉大的作品,將中國的詩書琴畫,飲食建筑盡融其中,每個人在其中都可以找到自己感興趣的部分。
你應該猜到了,我們此次的例子是女將軍的戰爭場面。戰爭一般說來肯定離不開男人,所以男性將軍也需要保留。但女將軍的作戰處理可能和男性并不相同,所以我們會她們單獨的提供一個類,而女將軍和男將軍會有一個共同的接口。先來看類圖:
假設在沒有繼承時有這么一個函數:
~~~
void War()
{
General *pGeneral = new General();
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
在有了繼承的情況下,該函數就可能會變成以下的情況:
~~~
void War(string strType)
{
General *pGeneral ;
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
delete pGeneral;
}
~~~
想象一下,創建對象的地方很可能并不只一處。那么你就可能在很多地方看到類似的代碼,想象一下如果我們再增加一些少年將軍,神仙將軍一類的基類,那你是不是需要一個一個找到這樣的函數去改?(好多時候遇到這樣的情況,開發人員怪用戶提的要求變態,確很少考慮自己的程序擴展性不強。)為了避免這種情況,最簡單的方法就是將類的創建過程獨立為一個單獨的類,如下:
~~~
class GeneralFactory
{
public:
static General *CreateGeneral(string strType)
{
if ( strType == “GirlGeneral”)
{
pGeneral = new GirlGeneral();
}
else if ( strType == “ManGeneral”)
{
pGeneral = new ManGeneral();
}
}
};
~~~
這樣我們的創建代碼被放到了一個統一的地方,在類進行擴展的時候,就可以方便的進行修改了。也避免了代碼的冗余。不過這個方法并沒有歸于GOF的設計模式中,我們以下將談Factory模式。
再考慮一個問題,我們的War是出現在一個類中,如我們有一個軍隊類,而軍隊分為男軍和女軍。男軍的將領肯定是男將軍,而女軍的將領肯定是女將軍。而且General類只會在軍隊類中用到。那我們還有沒有必要寫一個創建類呢?答案是否定的。我們先來看類圖:
那么這時候,我們就可以將CreateGeneral放到Army類中,而通過它的子類來實現它。好的,我們還是先看看具體的代碼,這樣有助于你理解。
~~~
#include
#include
using namespace std;
class General
{
public:
virtual void Ready() = 0;
virtual void Advance() = 0;
virtual void Assault() = 0;
};
class GirlGeneral : public General
{
private:
string m_strName;
public:
GirlGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 女將軍 " << m_strName << "正在準備!"<< endl;
}
void Advance()
{
cout << " 女將軍 " << m_strName << "正在前進!"<< endl;
}
void Assault()
{
cout << " 女將軍 " << m_strName << "正在攻擊!"<< endl;
}
};
class ManGeneral : public General
{
private:
string m_strName;
public:
ManGeneral(string strName):m_strName(strName){}
void Ready()
{
cout << " 將軍 " << m_strName << "正在準備!"<< endl;
}
void Advance()
{
cout << " 將軍 " << m_strName << "正在前進!"<< endl;
}
void Assault()
{
cout << " 將軍 " << m_strName << "正在攻擊!"<< endl;
}
};
class Army
{
public:
void war(string strName)
{
cout << "戰斗步驟" << endl;
//此處調用虛函數,事實上調用的是子類的方法
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
//此處定義一個創建Geraral的類,而不具體實現
virtual General* CreateGeneral(string strName) = 0;
};
class GirlArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
};
class ManArmy : public Army
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
};
int main(int argc, char* argv[])
{
GirlArmy gArmy;
gArmy.war("姽婳將軍");
ManArmy mArmy;
mArmy.war("笨蛋將軍");
return 0;
}
~~~
代碼比較簡單,無需太多解釋,需要注意的就是在Army類中注釋過的地方就可以了。
下面我們來看GOF對于Factory模式的定義:定義一個用于創建對象的接口,讓子類決定實例化哪個類。Factory Method使一個類的實例化延遲到其子類。
GOF書中提供的一個例子機具代表性,是Factory的典型用法,我們來看看。
這是我們在文檔視中看到的典型做法,在MFC中也可以找到。Application并不是自己創建文檔,而是通過在子類中重載CreateDocument來實現。Factory模式,我們的講解先到此。
我們再來考慮一個問題:我們的Army類存在與General同樣的問題,生成對象的時候需要考慮到它的類型。那我們是否需要象上面提到的GeneralFactory一樣,為它也提供一個類廠呢?接著擴展我們的想象,由于女將的加入,她們使用的兵器,坐騎以至戰爭場景等內容也許都需要做相應的擴展,那么我們是否需要這么多類都提供一個類廠呢?我們可以注意到所有這些擴展的類都有相似的功能都是為女性提供的,那么我們就可以給它們提供一個統一的類來實現創建過程。該方式就是Abstract Factory模式,我們首先來看看該模式。
Abstract Factory模式的定義是:提供一個創建一系列產品或者相互依賴對象的接口,而無需調用處指定具體的類。接著看看Abstract Factory的類圖:
圖中AbstractFactory是一個抽象接口,定義了創建不同產品的虛擬函數。而ConcreteFactory1和ConcreteFactory2是我們用到的具體的生成產品的類,圖上標的虛線表示它們具體要生成的那個類的對象。而AbstractProcutA和AbstractProcutB是抽象產品接口,相當于我們的Army和General接口。而ConcreteProcutA1和ConcreProcutB1是具體的產品類,我們的例子中相當于GirlArmy和GirlGeneral。而ConcreteProcutA2和ConcreProcutB2就相于ManArmy和ManGeneral了。
接著看我們的例子,此處就不畫類圖了。直接看看代碼:
~~~
class AbstractFactory
{
public:
virtual General* CreateGeneral() = 0;
virtual Army* CreateArmy() = 0;
};
class GirlFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
class ManFactory : public AbstractFactory
{
public:
General *CreateGeneral(string strName)
{
return new ManGeneral(strName);
}
Army *CreateArmy()
{
return new ManArmy();
}
};
~~~
這樣在我們的例子中的函數:
~~~
void war(string strName)
{
cout << "戰斗步驟" << endl;
General *pGeneral = CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
可能變為
~~~
void war(AbstractFactory *pFactory,string strName)
{
cout << "戰斗步驟" << endl;
General *pGeneral = AbstractFactory ->CreateGeneral(strName);
pGeneral->Ready();
pGeneral->Advance();
pGeneral->Assault();
cout << endl;
}
~~~
Abstract Factory在現實開發中用的比較多的情況是在處理數據庫的時候,如果你的程序需要同時支持oracle和SQL Server數據庫,那樣幾乎所有的數據操作類都需要兩套代碼。那么使用Abstract Factory模式,只需通過配置文件生成不同的創建函數,就可以實現不同數據庫的實現了。
事實上Abstract Factory相當于Factory模式的擴展,都是基類定義一個創建對象的方法,而由子類來實現。
Abstract Factory模式也可以使用PROTOTYPE(原型)模式來實現,PROTOTYPE的定義是:用原型實例來創建對象的類。說白了就是通過拷貝構造函數來實現新類的創建。這里就不多講了。
還有Builder模式,其實和Abstract Factory很相似,但它強調的內容不同,它強調的是一步一步要創建對象,也就是說它會幫助對象創建一些內容。而Abstract Factory是直接返回對象。
接著考慮,我們想把我們的工廠類作成一個單件類型,這樣的話,由于工廠類只有一個對象,我們就容易對工廠對象所要創建的對象做些控制,如可以限定對象的生成數,控制對象的生成時機等。我們不能象上節一樣直接把CreateGeneral定義成static型,因為這樣就沒有動態的特性。那用什么方法呢?還是看代碼:
~~~
class GirlFactory : public AbstractFactory
{
private:
static GirlFactory *instance;
GirlFactory(){}
public:
static GirlFactory* GetInstance()
{
if ( instance == NULL )
{
instance = new GirlFactory();
}
return instance;
}
General *CreateGeneral(string strName)
{
return new GirlGeneral(strName);
}
Army *CreateArmy()
{
return new GirlArmy();
}
};
GirlFactory *GirlFactory::instance = NULL;
~~~
我們的類工廠中,構造函數GirlFactory被設置成了私有成員函數,這樣只有它本身才能調用。我們再填加一個靜態成員變量instance,這樣保證了只有一個對象。而后再定義一個靜態函數GetInstance,它返回所需要的類對象。
這樣我們對類對象的調用就成了以下的形式:
GirlFactory *pGirlFactory = GirlFactory::GetInstance();
好了,這就是單件模式了。這次就先講這么多了。
參考書目:
1,設計模式——可復用面向對象軟件的基礎(Design Patterns ——Elements of Reusable Object-Oriented Software) Erich Gamma 等著 李英軍等譯 機械工業出版社
2,Head First Design Patterns(影印版)Freeman等著 東南大學出版社
3,道法自然——面向對象實踐指南 王詠武 王詠剛著 電子工業出版社
4,紅樓夢(脂批匯校本) —— 網上找到的電子檔