設計模式之工廠模式與抽象工廠模式


 

於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)方法的抽象工廠模式較好,所以繼承方案應該是后一種要好一些

 

四,結語

經過之前的分析,對工廠模式和抽象工廠模式有了一定的認識,可能還存在不足之處,望讀者指出,一同探討,另外,本人文筆較拙,想到哪里寫到哪里,還望讀者指正紕漏!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM