剛接觸設(shè)計(jì)模式的第一課,工廠模式與抽象工廠,確實(shí)感覺到了設(shè)計(jì)模式的抽象與強(qiáng)大作用力,學(xué)習(xí)過程中自己動(dòng)手設(shè)計(jì)了一個(gè)小游戲的角色。
假定一個(gè)游戲中有很多怪物角色,如蜘蛛,馬,猴子,等,此游戲有多種游戲級(jí)別,先假定為3級(jí)。
設(shè)計(jì)游戲時(shí)必須考慮到:
1,游戲的角色可擴(kuò)展性
2,游戲易于維護(hù)(如,游戲中怪物角色易于管理)
暫時(shí)不考慮其他的問題,先說說角色的設(shè)計(jì)問題。
游戲中有很多怪物角色,也分為三級(jí),即怪物也有三個(gè)級(jí)別,那么,怎么設(shè)計(jì)角色的繼承體系呢?
至少有以下兩種策略:
a),游戲維護(hù)一個(gè)怪物超類,所有怪物直接繼承,將這些子類再作為超類,供“三個(gè)級(jí)別”怪物類繼承,類結(jié)構(gòu)大致為:
CMonster
CHouse :public CMonster,......
CHighLevelHouse :public CHouse,CLowLevelHouse :public CHouse.....
b),游戲維護(hù)一個(gè)怪物超類,讓三個(gè)級(jí)別繼承此超類,再分別讓每個(gè)怪物繼承之,類結(jié)構(gòu)大致為:
CMonster
CHighLevelMonster : public CMonster,CLowLevelMonster : public CMonster.......
CHighLevelHouse : public CHighLevelMonster,CLowLevelHouse: public CLowLevelMonster....
暫且不論這兩種繼承方案怎樣,下面就這兩種方案分別使用工廠模式和抽象工廠得到怪物對(duì)象。
一,使用工廠模式
使用工廠模式的目的在于代碼的客戶不需要親自實(shí)例化一個(gè)對(duì)象,客戶只需要操心:“我要得到什么”,而不用操心:“我要使用哪一個(gè)具體的類去獲得”;考慮在可預(yù)見的將來客戶需要的對(duì)象改變了,那么,他是否不得不去更改那一段new Chouse()的代碼?但如果使用工廠方法,替代這種“硬編碼”,或言之“過程性編碼”,則他不需要更改任何代碼,而只需要更改工廠里生產(chǎn)此對(duì)象的方法,將生產(chǎn)的對(duì)象替換一下就能做到。這是很具有優(yōu)越性的,假定程序中出現(xiàn)了一萬(wàn)次new Chouse(),后來我們不再需要Chouse這個(gè)對(duì)象,而是需要重寫或者有較Chouse有大更改的ChouseChanged 類實(shí)例,這就意味著一天悲慘的查找->替換工作的開始!但如果使用了工廠方法,以一種封閉的方法產(chǎn)生客戶需要的對(duì)象,就只需要在工廠內(nèi)部修改生成的方法,替換一次即可!
以上是使用的目的!但如何使用呢,下面就來深入的了解工廠模式。工廠從某種角度上就是一種透明的機(jī)器,這種機(jī)器有很多型號(hào),有的能生產(chǎn)猴子對(duì)象,有的能生成蜘蛛對(duì)象,等等。那么,為什么不是“一部透明的機(jī)器”呢,因?yàn)樗a(chǎn)很多種對(duì)象,“一部機(jī)器”是不能生產(chǎn)末知種類,末知數(shù)量的對(duì)象的。那,為什么不將某參數(shù)傳入此機(jī)器,讓它能根據(jù)參數(shù)生產(chǎn)特定的對(duì)象呢?答案在于,可擴(kuò)展性。如前所言,工廠并不知道它要生產(chǎn)怎樣的對(duì)象,生產(chǎn)多少種類型的對(duì)象,如果單純地以參數(shù)去界定,若以后再增加了一/多種對(duì)象,則還需要修改工廠的生產(chǎn)方法,可能是增加switch里面的case語(yǔ)句,這樣就涉及到維護(hù)問題了,一旦忘記,則導(dǎo)致新加的對(duì)象創(chuàng)建不成功!記住,工廠本應(yīng)是一個(gè)抽象的概念,不是一個(gè)具體的概念。所以生產(chǎn)多種對(duì)象,必須有多種具體的工廠!如,生產(chǎn)猴子的猴子工廠,生產(chǎn)蜘蛛的蜘蛛工廠,等。這就是工廠模式。
二,使用抽象工廠模式
再來說說抽象工廠模式,其實(shí)本人覺得抽象工廠模式也就是工廠模式,只是它是前一種模式復(fù)雜一些的模式,但本質(zhì)還是一樣的,如果你會(huì)用工廠模式,那么你一定會(huì)用抽象工廠模式!
已經(jīng)知道了為什么要使用工廠模式,現(xiàn)在讓我們開始分析在那個(gè)游戲中,怎樣通過工廠模式去獲得怪物。
現(xiàn)在的情況稍微復(fù)雜了一些,增加了游戲的難易級(jí)別,則不能簡(jiǎn)單的直接使用工廠模式,因?yàn)闊o(wú)法滿足三個(gè)級(jí)別的限制,那么,抽象工廠便出現(xiàn)了,其實(shí)質(zhì)是將工廠再次向上抽象,產(chǎn)生繼承得到多個(gè)抽象的工廠。
考慮將工廠歸類,則至少有以下兩種策略:
a),定義三個(gè)“級(jí)別工廠”:高級(jí)怪物工廠,中級(jí)怪物工廠,低級(jí)怪物工廠,讓每個(gè)工廠去生產(chǎn)所有類型的怪物
b),定義多個(gè)“怪物工廠”:猴子工廠,蜘蛛工廠,馬工廠,讓每個(gè)工廠去生產(chǎn)三種怪物(高級(jí),中級(jí),低級(jí))
首先,我們先決策哪一種策略更優(yōu),把“更優(yōu)”換一種說法,即是文章最開始說到的兩個(gè)條件:可擴(kuò)展性,可維護(hù)性。先考察可擴(kuò)展性:假設(shè)游戲以后增加了幾十種,幾百種,新的怪物,則b中的怪物工廠則飆增到相同的數(shù)目,相比之下 a 中的工廠數(shù)目則不會(huì)增加。但 a 也會(huì)付出慘重的代價(jià):每個(gè)工廠里面,增加幾十個(gè),幾百個(gè)生產(chǎn)對(duì)象的方法。有一個(gè)經(jīng)驗(yàn),“集中地增加代碼,而不是分散地增加或者修改既有代碼”往往表現(xiàn)出更優(yōu)的可擴(kuò)展性。顯然,定義怪物工廠可獲得更好的可擴(kuò)展性。因?yàn)樵赽中,新增加一種怪物,只需要增加一個(gè)工廠,同時(shí)在里面寫入三個(gè)方法分別產(chǎn)生三種級(jí)別的怪物即可,相比a中的在三個(gè)工廠中都增加一個(gè)方法(不能遺忘),顯然更優(yōu)一些!但b也不是最優(yōu)解,因?yàn)槿绻螒蚣?jí)別一旦增加,則需要在每個(gè)怪物工廠中去增加相應(yīng)的代碼,而a中則只需要再新增加一個(gè)工廠,并將其它工廠里的代碼直接拷到下面即可工作。但我們之前有約定:怪物的易變性大于級(jí)別的易變性,畢竟,一個(gè)游戲的游戲級(jí)別是不會(huì)常變的,這也符合假定!如果要徹底解決這個(gè)問題,我們得時(shí)刻記住一句話:“永遠(yuǎn)要對(duì)變化的東西抽象(封裝)”。于是,我們自然會(huì)想到,使用繼承去獲得游戲級(jí)別變化下的可擴(kuò)展性,我們可以在b的基礎(chǔ)上再使用一層抽象,讓怪物工廠里的生產(chǎn)怪物的方法是抽象的,(C++里使用vitual修飾),讓每個(gè)怪物工廠被三個(gè)類(目前是三個(gè)級(jí)別)去繼承,可能是這樣的類結(jié)構(gòu):
CFactory
CHouseFactory : public CFactory,CMonkeyFactory : public CFactrory,.......
CHighLevelFactory : public CHosueFactory,CLowLevelFactory : public CHouseFactory....
這樣會(huì)獲得完全的擴(kuò)展性,但與之相應(yīng)的代價(jià)是子類膨脹問題,子類巨多!每個(gè)怪物工廠類有三個(gè)子類!
這里可以做權(quán)衡,減少可擴(kuò)展性,增加新函數(shù)代替子類:可能的結(jié)構(gòu)如下:
CFactory
{
virtual CHighLevelMonster* getHighLevelMonster();
virtual CMiddleLevelMonster* getMiddleLevelMonster();
virtual CLowLevelMonster* getLowLevelMonster();
//可能只有這三種級(jí)別
}
CHouseFactory : public CFactory
{
CHighLevelMonster* getHighLevelMonster();
CMiddleLevelMonster* getMiddleLevelMonster();
CLowLevelMonster* getLowLevelMonster();
}
說明:
1,在超類工廠CFactory中任何獲得對(duì)象的方法都是virtual的
2,抽象工廠中的獲得對(duì)象的方法的返回值類型必定不同!(第一個(gè)是CHighLevelMonster*,第二個(gè)是CMiddleLevelMonster*,第三個(gè)是CLowLevelMonster*)。謹(jǐn)記:如果它們的返回值一樣,則退化為了工廠模式,而不是抽象工廠模式!之所以叫抽象工廠,是因?yàn)槌橄蠊S一旦實(shí)例化,則可以實(shí)例化為多種類型的工廠,每一種工廠用來生產(chǎn)相關(guān),但不同類型的對(duì)象,各種實(shí)例化出來的工廠之間的差別在于,它們生產(chǎn)這些對(duì)象的方式或者結(jié)果不同!游戲需要三種級(jí)別的怪物,則每個(gè)工廠按自己的方式去生成它們,(“不同的方式”體現(xiàn)在,每個(gè)工廠生產(chǎn)出的怪物(即使級(jí)別相同,但)不同)
三,反過來再談繼承方案
在文章的最開始,我們提出了兩種繼承策略,
a),游戲維護(hù)一個(gè)怪物超類,所有怪物直接繼承,將這些子類再作為超類,供“三個(gè)級(jí)別”怪物類繼承,類結(jié)構(gòu)大致為:
CMonster
CHouse :public CMonster,......
CHighLevelHouse :public CHouse,CLowLevelHouse :public CHouse.....
b),游戲維護(hù)一個(gè)怪物超類,讓三個(gè)級(jí)別繼承此超類,再分別讓每個(gè)怪物繼承之,類結(jié)構(gòu)大致為:
CMonster
CHighLevelMonster : public CMonster,CLowLevelMonster : public CMonster.......
CHighLevelHouse : public CHighLevelMonster,CLowLevelHouse: public CLowLevelMonster....
我們前面使用的抽象工廠模式正是基于的第二種繼承方案,所以被抽象的工廠是“怪物工廠”,而不是“級(jí)別工廠”,然后在各個(gè)工廠里生產(chǎn)出的是不同級(jí)別的怪物。若我們使用第一種繼承方案,由正好對(duì)應(yīng)了將工廠歸類中的 a) ,定義三個(gè)“級(jí)別工廠”:高級(jí)怪物工廠,中級(jí)怪物工廠,低級(jí)怪物工廠,讓每個(gè)工廠去生產(chǎn)所有類型的怪物此時(shí),形成的是“級(jí)別工廠”,在每個(gè)級(jí)別工廠里,生產(chǎn)出的是級(jí)別相同但屬性不同的怪物,可能的代碼是這樣的:
CFactory
{
virtual CMonkey* getMonkey();
virtual CHouse* getHouse();
virtual CSpiter* getSpiter();
.............//省略掉其它怪物
}
CHighLevelFactory : public CFactory
{
CMonkey* getMonkey();
CHouse* getHouse();
CSpiter* getSpiter();
....................
}
根據(jù)之前的討論,b)方法的抽象工廠模式較好,所以繼承方案應(yīng)該是后一種要好一些
四,結(jié)語(yǔ)
經(jīng)過之前的分析,對(duì)工廠模式和抽象工廠模式有了一定的認(rèn)識(shí),可能還存在不足之處,望讀者指出,一同探討!