於2012/12/05完成原廠模式與抽象原廠模式
剛接觸設計模式的第一課,原廠模式與抽象工廠,確實感覺到了設計模式的抽象與強大作用力,學習過程中自己動手設計了一個小遊戲的角色。
假定一個遊戲中有很多怪物角色,如蜘蛛,馬,猴子,等,此遊戲有多種遊戲層級,先假定為3級。
設計遊戲時必須考慮到:
1,遊戲的角色可擴充性
2,遊戲易於維護(如,遊戲中怪物角色易於管理)
暫時不考慮其他的問題,先說說角色的設計問題。
遊戲中有很多怪物角色,也分為三級,即怪物也有三個層級,那麼,怎麼設計角色的繼承體系呢?
至少有以下兩種策略:
a),遊戲維護一個怪物超類,所有怪物直接繼承,將這些子類再作為超類,供“三個層級”怪物類繼承,類結構大致為:
CMonster
CHorse :public CMonster,......
CHighLevelHorse :public CHorse,CLowLevelHorse :public CHorse.....
b),遊戲維護一個怪物超類,讓三個層級繼承此超類,再分別讓每個怪物繼承之,類結構大致為:
CMonster
CHighLevelMonster : public CMonster,CLowLevelMonster : public CMonster.......
CHighLevelHorse : public CHighLevelMonster,CLowLevelHorse: public CLowLevelMonster....
暫且不論這兩種繼承方案怎樣,下面就這兩種方案分別使用原廠模式和抽象工廠得到怪物對象。
一,使用原廠模式
使用原廠模式的目的在於代碼的客戶不需要親自執行個體化一個對象,客戶只需要操心:“我要得到什麼”,而不用操心:“我要使用哪一個具體的類去獲得”;考慮在可預見的將來客戶需要的對象改變了,那麼,他是否不得不去更改那一段new CHorse()的代碼?但如果使用Factory 方法,替代這種“寫入程式碼”,或言之“過程性編碼”,則他不需要更改任何代碼,而只需要更改工廠裡生產此對象的方法,將生產的對象替換一下就能做到。這是很具有優越性的,假定程式中出現了一萬次new CHorse(),後來我們不再需要CHorse這個對象,而是需要重寫或者有較CHorse有大更改的CHorseChanged 類執行個體,這就意味著一天悲慘的尋找->替換工作的開始!但如果使用了Factory 方法,以一種封閉的方法產生客戶需要的對象,就只需要在工廠內部修改產生的方法,替換一次即可!
以上是使用的目的!但如何使用呢,下面就來深入的瞭解原廠模式。工廠從某種角度上就是一種透明的機器,這種機器有很多型號,有的能生產猴子物件,有的能產生蜘蛛對象,等等。那麼,為什麼不是“一部透明的機器”呢,因為它要生產很多種對象,“一部機器”是不能生產末知種類,末知數量的對象的。那,為什麼不將某參數傳入此機器,讓它能根據參數生產特定的對象呢?答案在於,可擴充性。如前所言,工廠並不知道它要生產怎樣的對象,生產多少種類型的對象,如果單純地以參數去界定,若以後再增加了一/多種對象,則還需要修改工廠的生產方法,可能是增加switch裡面的case語句,這樣就涉及到維護問題了,一旦忘記,則導致新加的對象建立不成功!記住,工廠本應是一個抽象的概念,不是一個具體的概念。所以生產多種對象,必須有多種具體的工廠!如,生產猴子的猴子工廠,生產蜘蛛的蜘蛛工廠,等。這就是原廠模式。
二,使用抽象原廠模式
再來說說抽象原廠模式,其實本人覺得抽象原廠模式也就是原廠模式,只是它是前一種模式複雜一些的模式,但本質還是一樣的,如果你會用原廠模式,那麼你一定會用抽象原廠模式!
已經知道了為什麼要使用原廠模式,現在讓我們開始分析在那個遊戲中,怎樣通過原廠模式去獲得怪物。
現在的情況稍微複雜了一些,增加了遊戲的難易層級,則不能簡單的直接使用原廠模式,因為無法滿足三個層級的限制,那麼,抽象工廠便出現了,其實質是將工廠再次向上抽象,產生繼承得到多個抽象的工廠。
考慮將工廠歸類,則至少有以下兩種策略:
a),定義三個“層級工廠”:進階怪物工廠,中級怪物工廠,低級怪物工廠,讓每個工廠去生產所有類型的怪物
b),定義多個“怪物工廠”:猴子工廠,蜘蛛工廠,馬工廠,讓每個工廠去生產三種怪物(進階,中級,低級)
首先,我們先決策哪一種策略更優,把“更優”換一種說法,即是文章最開始說到的兩個條件:可擴充性,可維護性。先考察可擴充性:假設遊戲以後增加了幾十種,幾百種,新的怪物,則b中的怪物工廠則飆增到相同的數目,相比之下 a 中的工廠數目則不會增加。但 a 也會付出慘重的代價:每個工廠裡面,增加幾十個,幾百個生產對象的方法。有一個經驗,“集中地增加代碼,而不是分散地增加或者修改既有代碼”往往表現出更優的可擴充性。顯然,定義怪物工廠可獲得更好的可擴充性。因為在b中,新增加一種怪物,只需要增加一個工廠,同時在裡面寫入三個方法分別產生三種層級的怪物即可,相比a中的在三個工廠中都增加一個方法(不能遺忘),顯然更優一些!但b也不是最優解,因為如果遊戲層級一旦增加,則需要在每個怪物工廠中去增加相應的代碼,而a中則只需要再新增加一個工廠,並將其它工廠裡的代碼直接拷到下面即可工作。但我們之前有約定:怪物的易變性大於層級的易變性,畢竟,一個遊戲的遊戲層級是不會常變的,這也符合假定!如果要徹底解決這個問題,我們得時刻記住一句話:“永遠要對變化的東西抽象(封裝)”。於是,我們自然會想到,使用繼承去獲得遊戲層級變化下的可擴充性,我們可以在b的基礎上再使用一層抽象,讓怪物工廠裡的生產怪物的方法是抽象的,(C++裡使用vitual修飾),讓每個怪物工廠被三個類(目前是三個層級)去繼承,可能是這樣的類結構:
CFactory
CHorseFactory : public CFactory,CMonkeyFactory : public CFactrory,.......
CHighLevelHorseFactory : public CHosueFactory,CLowLevelHorseFactory : public CHorseFactory....
這樣會獲得完全的擴充性,但與之相應的代價是子類膨脹問題,子類巨多!每個怪物工廠類有三個子類!
這裡可以做權衡,減少可擴充性,增加新函數代替子類:可能的結構如下:
CFactory
{
virtual CHighLevelMonster* getHighLevelMonster();
virtual CMiddleLevelMonster* getMiddleLevelMonster();
virtual CLowLevelMonster* getLowLevelMonster();
//可能只有這三種層級
}
CHorseFactory : public CFactory
{
CHighLevelMonster* getHighLevelMonster();
CMiddleLevelMonster* getMiddleLevelMonster();
CLowLevelMonster* getLowLevelMonster();
}
說明:
1,在超類工廠CFactory中任何獲得對象的方法都是virtual的
2,抽象工廠中的獲得對象的方法的傳回值類型必定不同!(第一個是CHighLevelMonster*,第二個是CMiddleLevelMonster*,第三個是CLowLevelMonster*)。謹記:如果它們的傳回值一樣,則退化為了原廠模式,而不是抽象原廠模式!之所以叫抽象工廠,是因為抽象工廠一旦執行個體化,則可以執行個體化為多種類型的工廠,每一種工廠用來生產相關,但不同類型的對象,各種執行個體化出來的工廠之間的差別在於,它們生產這些對象的方式或者結果不同!遊戲需要三種層級的怪物,則每個工廠按自己的方式去產生它們,(“不同的方式”體現在,每個工廠生產出的怪物(即使層級相同,但)不同)
三,反過來再談繼承方案
在文章的最開始,我們提出了兩種繼承策略,
a),遊戲維護一個怪物超類,所有怪物直接繼承,將這些子類再作為超類,供“三個層級”怪物類繼承,類結構大致為:
CMonster
CHorse :public CMonster,......
CHighLevelHorse :public CHorse,CLowLevelHorse :public CHorse.....
b),遊戲維護一個怪物超類,讓三個層級繼承此超類,再分別讓每個怪物繼承之,類結構大致為:
CMonster
CHighLevelMonster : public CMonster,CLowLevelMonster : public CMonster.......
CHighLevelHorse : public CHighLevelMonster,CLowLevelHorse: public CLowLevelMonster....
我們前面使用的抽象原廠模式正是基於的第二種繼承方案,所以被抽象的工廠是“怪物工廠”,而不是“層級工廠”,然後在各個工廠裡生產出的是不同層級的怪物。若我們使用第一種繼承方案,由正好對應了將工廠歸類中的 a) ,定義三個“層級工廠”:進階怪物工廠,中級怪物工廠,低級怪物工廠,讓每個工廠去生產所有類型的怪物此時,形成的是“層級工廠”,在每個層級工廠裡,生產出的是層級相同但屬性不同的怪物,可能的代碼是這樣的:
CFactory
{
virtual CMonkey* getMonkey();
virtual CHorse* getHorse();
virtual CSpiter* getSpiter();
.............//省略掉其它怪物
}
CHighLevelFactory : public CFactory
{
CMonkey* getMonkey();
CHorse* getHorse();
CSpiter* getSpiter();
....................
}
根據之前的討論,b)方法的抽象原廠模式較好,所以繼承方案應該是後一種要好一些
四,結語
經過之前的分析,對原廠模式和抽象原廠模式有了一定的認識,可能還存在不足之處,望讀者指出,一同探討,另外,本人文筆較拙,想到哪裡寫到哪裡,還望讀者指正紕漏!