於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()的代碼?但如果使用工廠方法,替代這種“硬編碼”,或言之“過程性編碼”,則他不需要更改任何代碼,而只需要更改工廠里生產此對象的方法,將生產的對象替換一下就能做到。這是很具有優越性的,假定程序中出現了一萬次new CHorse(),后來我們不再需要CHorse這個對象,而是需要重寫或者有較CHorse有大更改的CHorseChanged 類實例,這就意味着一天悲慘的查找->替換工作的開始!但如果使用了工廠方法,以一種封閉的方法產生客戶需要的對象,就只需要在工廠內部修改生成的方法,替換一次即可!
以上是使用的目的!但如何使用呢,下面就來深入的了解工廠模式。工廠從某種角度上就是一種透明的機器,這種機器有很多型號,有的能生產猴子對象,有的能生成蜘蛛對象,等等。那么,為什么不是“一部透明的機器”呢,因為它要生產很多種對象,“一部機器”是不能生產末知種類,末知數量的對象的。那,為什么不將某參數傳入此機器,讓它能根據參數生產特定的對象呢?答案在於,可擴展性。如前所言,工廠並不知道它要生產怎樣的對象,生產多少種類型的對象,如果單純地以參數去界定,若以后再增加了一/多種對象,則還需要修改工廠的生產方法,可能是增加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)方法的抽象工廠模式較好,所以繼承方案應該是后一種要好一些
四,結語
經過之前的分析,對工廠模式和抽象工廠模式有了一定的認識,可能還存在不足之處,望讀者指出,一同探討,另外,本人文筆較拙,想到哪里寫到哪里,還望讀者指正紕漏!