標籤:
作者: firedragonpzy 原地址:http://www.firedragonpzy.com.cn/index.php/archives/1781
本系列文章我將和大家一起來發掘cocos2d-x中所使用到的設計模式,同樣的,這些模式在cocos2d-iphone中也可以找到其身影。
聲明:這裡發掘模式只是我的個人愛好,通過這個過程,我希望能加深自己對於設計模式運用的理解。關於模式的學習,市面上已經有許多非常好的書籍了。比如《Head First設計模式》、GoF的設計模式,還有《研磨設計模式》等。如果讀者對於設計模式完全不瞭解的話,建議先閱讀上面至少一本書籍,瞭解設計模式之後再閱讀本系列文章。這樣大家才會有相互交流的共同語言。
為什麼要發掘設計模式呢?因為設計模式本身就是人們在一些物件導向的軟體系統裡面發掘出來的,在一定的情境之下可以重用的解決方案。通過對模式的挖掘,我可以藉此機會學習一下這些優秀的設計思想。因為我覺得,一個好的開源遊戲架構除了能給我們開發人員帶來開發效率的提升以外,還應該被我們吸收其設計思想,這樣它的價值才能完整。
本系列文章將按照如下思路進行模式挖掘:
1.找出某個設計模式的應用情境(類、類結構、對象結構,必要時結合UML類圖)
2.分析為什麼要使用此模式(即設計決策)
3.使用此模式的優缺點是什麼(任何事務都有兩面性,設計模式也不例外)
4.此模式的定義及一般實現(這個在GoF的經典書籍裡面有,這裡借用一下)
5.在遊戲開發中如何運用此模式(自己對於模式運用情境的理解)
6.此模式經常與哪些模式配合使用(這個也基本是從GoF的書籍裡面借用了)
1.Cocos2D-x中的單例如下:
- CCDirector
- CCTextureCache
- CCSpriteFrameCache
- CCAnimationCache
- CCUserDefault
- CCNotificationCenter
- CCShaderCache
- CCScriptEngineManager
- CCPoolManager
- CCFileUtils
- CCProfiler
- SimleAudioEngie
- CCConfiguration
- CCApplication
- CCDirectorCaller(ios平台)
- CCEGLView
為什麼會存在這樣一些單例呢?
首先是CCDirector單例,它負責管理初始化OpenGL渲染視窗以及遊戲情境的流程式控制制,它是cocos2dx遊戲開發中必不可少的類之一。為什麼要把此類設計成單例對象呢?因為,一個遊戲只需要有一個遊戲視窗就夠了,所以,只需要初始化一次OpenGL渲染視窗。而且情境的流程式控制制功能,也只需要存在一個這樣的情境控制對象即可。為了保證CCDirector類只存在一個執行個體對象,就必須使用單例模式。
接下來是CCTextureCache單例。此類主要負責載入遊戲當中所需要的紋理圖片資源,這些資源載入好以後,就可以一直保留在記憶體裡面,當下次再需要使用此紋理的時候,直接返回相應的紋理對象引用就可以了,無需再重複載入。當然,這樣做可能會很浪費記憶體,所以cocos2dx採用了一種引用計數的方式來管理對象記憶體,當紋理剛被載入進來的時候,引用計數為1。如果使用此紋理對象建立一個精靈,那麼此紋理對象引用會加1.如果精靈被釋放,則相應的引用計數減1.當紋理的引用計數變為0的時候,紋理所佔用的記憶體自然就會被釋放掉。這也是為什麼在收到記憶體警告的時候,會調用CCTextureCache的 removeUnusedTextures方法。此方法會將所有引用計數為1的紋理對象全部釋放掉。單從字面上看,Cache,即緩衝的意思。它以犧牲一定的記憶體壓力為代價,帶來的是遊戲效能的提升。這種cache技術,在遊戲開發中比比皆是。註:IO操作對遊戲效能影響非常大,要極力避免!!!
類似的CCSpriteFrameCache、CCAnimationCache和CCShaderCache,它們也都是緩衝類,分別負責緩衝SpriteFrame、Animation和Shader。這樣做的原因無非就是為了效能,以空間換時間。
接下來,看看CCUserDefault。此類主要是用來儲存遊戲中的資料用的,它會建立一個xml檔案,並把使用者自訂的資料以key-value的形式儲存到此xml檔案中。此類為什麼會變成單例類呢?原因也很簡單,因為類似這種操作資料檔案,或者設定檔的類,通常只需要在程式運行過程中存在一個執行個體即可。
接著是CCNotificationCenter,這是一個通知中樞,它其實還運用了一個觀察者模式,這裡暫時不討論。該通知中樞理論上也是只需要一個就夠了。但是,cocos2d-x在實現此單例的時候,並沒有將此類的建構函式私人麼,我在猜想,是不是開發人員有意為之呢?或者多個通知中樞也有其存在的價值。這個大家可以討論一下。
CCScriptEngineManager,此類包含一個實現了CCScriptEngineProtocl介面的對象引用,它可以協助我們方便地找到LuaEngine對象。這裡單例的作用純粹變成了LuaEngine的一個全域訪問點了。為什麼不直接把LuaEngine作為單例對象呢?是否在某些情況之下,可能需要建立多個LuaEngine對象?如果考慮到cocos2d-x還可以同時支援其它的指令碼引擎,那也可以相應的把另外的指令碼引擎設計成單例類。當然,這樣做無疑會使引擎裡面的單例過多。考慮到單例模式近年來被廣大開發人員所詬病,已將其列入“反模式”。這裡引用CCScriptEngineManager單例類,給其它引擎對象提供訪問的惟一全域點,這也不失為一個辦法。不知我的推測是否正確?
CCPoolManager,此類是用來管理AutoReleasePool對象棧的。因為cocos2d-x採用的是基於引用計數的方式來管理動態記憶體,所以採用引用計數的被託管對象都被放入一個當前的autoReleasePool裡面去了。當CCDirector的mainLoop每次更新的時候,都會調用CCPoolManager的pop方法,把當前autoReleasePool裡面的所有autoRelease對象的託管狀態設定為false,同時把該autoReleasePool清空,而清空的時候則會調用 autoReleasePool裡面所有對象的release方法來釋放記憶體。此類為什麼要設計與單例呢? 因為多個地方需要引用此類,為了方便引用,所以設計成單例。
然後是CCFileUtils類。該類是一個工具類。工具類和設定檔類,它們絕大多數情況也都是設計成單例的。因為它們沒有存在多個執行個體的必要。同時,它們也可以實現為一組類方法,這樣無需建立對象也能夠使用。
然後是CCProfiler類,該類負責cocos2d的效能其運行情況分析,也是一個工具類。所以它設計成單例類的理由與CCFileUtils類差不多。
CCConfiguration類也被設計成了單例對象,此類主要負責管理cocos2d-x的一些OpenGL變數資訊。這些資訊本可以通過定義一些宏,或者通過一些全域變數來解決的。這裡設計成單例類也是更加“物件導向”的體現。因為這些配置資訊根本不需存在多個對象。
CCApplication類的設計初衷是獲得平台相關的一些資訊,最重要的是運行遊戲的主迴圈(main loop)。一個遊戲只需要一個應用程式執行個體,所以設計與單例。
CCEGLView是Cocos2d-x對於EGLView的抽象,不同的平台會有不同的實現,使之可以適用不同的平台。在ios平台上面它是對EAGLView的一個簡單的封裝。該類表示的是對OpenGL渲染上下文視窗的一種抽象,這是一種虛擬資源,而且只有存在一份執行個體的可能,所以被設計成單例類。
CCDirectorCaller類是ios平台相關的類,就是對ios平台CCDirector對象的一個封裝,使用的是CADisplayLink來運行遊戲主迴圈。該類和CCDirector類差不多,也可以設計成單例。為什麼會在CCApplication類裡面調用CCDirectorCaller類,是基於分離平台相關代碼的考慮。CCApplication是的,CCDirectorCaller也是的。
最後一個是SimpleAudioEngine類,它也被設計成了一個單例類。因為它提供給了開發人員最簡單的聲音操作介面,可以方便地處理遊戲中的背景音樂和音效。此類同時還應用了面板模式,把CocoDenshion子系統中的複雜功能給屏蔽起來了,簡化了用戶端程式員的調用。該類為什麼要設計成單例,是因為到處都要訪問它。設計成單例會很方便,而且它 與其它對象沒有什麼聯絡,不好使用對象組合。
2.使用單例模式的優缺點
優點:簡單易用,限制一個類只有一個執行個體,可以降低建立多個對象可能會引起的記憶體問題的風險,包括記憶體流失、記憶體佔用問題。
缺點:單例模式因為提供了一個全域的訪問點,你可以在程式的任何地方輕而易取地訪問到,這本身就是一種高耦合的設計。一旦單例改變以後,其它模板都需要修改。另外,單例模式使得對象變成了全域的了。學過面對對象編程的人都知道,全域變數是非常邪惡的,要盡量不要使用。而且單例模式會使得對象的記憶體在程式結束之前一直存在,在一些使用GC的語言裡面,這其實就是一種記憶體流失,因為它們永遠都不到釋放。當然,也可以通過提供一些方法來釋放單例對象所佔用的記憶體,比如前面提到的XXXCache對象,都有相應的Purge方法。最後,cocos2dx裡面實現的單例,99%都不是安全執行緒的。
在討論優缺點的時候,讀者想必也看出來了,缺點比優點多多了。這是給大家提個醒,以後使用單例模式的時候要謹慎,不要濫用。因為此模式最容易被濫用。只有真正符合單例模式應用情境的時候,才能考慮。不要為了訪問方便,就把任何類都弄成單例,這樣,到最後,你會發現你的程式裡面就只剩下一堆單例和工廠了。
此外,單例模式正在消減,比如CCActionManager和CCTouchDispatcher在cocos2d1.0之前也是單例,現在變成了CCDirector類的屬性了。而且Riq(cocos2d-iphone的作者)也有在郵件中提到,以後CCDirector對象也會變成非單例,並且允許一個遊戲中建立多個遊戲視窗。
3.單例模式的定義:保證一個類僅有一個執行個體,並提供一個訪問它的全域的訪問點。
它的一般實現如下所示:
public class Singleton{ public: //全域訪問點 static Singleton* SharedSingleton() { if(NULL == m_spSingleton) { m_spSingleton = new Singleton(); } return m_spSingleton; } private: static Singleton* m_spSingleton; Singleton(); Singleton(const Singleton& other); Singleton& operator=(const Singleton& other);};Singleton* Singleton::m_spSingleton = NULL;
注意,這裡只是最基本的實現,它沒有考慮到安全執行緒,也沒有考慮記憶體釋放。但是,這個實現有兩個最基本的要素。
一:定義一個靜態變數,並把建構函式等設定為私人的。二:提供一個全域的訪問點給外部存取。
4.遊戲開發中如何運用此模式呢?眾所周知,遊戲開發中離不開遊戲資料儲存和載入。這些資料包括關卡資料、遊戲進行中的狀態資料等。這樣一些資訊很多遊戲模組中都需要訪問,所以可以為之設定一個單例對象。我武斷地認為,用戶端遊戲開發中,至少需要一個單例對象。因為一個全域的訪問點可以方便很多個物件之間的互動。根據之前的討論,也可以把一些時覺需要用到的類引用儲存在此單例對象中,不過只需要儲存弱引用即可。使用單例,最嚴重的就是怕記憶體流失,所以,大家盡量不要把單例類設計地太複雜,也不要讓它包含過多的動態記憶體管理工作。
5.單例模式一般與原廠模式配合使用,因為一般會將工廠類設計成單例對象。比如前面提到的各種cache類,它們也可以看作是某種意義上的工廠對象。由於工廠就是負責生產對象的,而cache類都可以根據使用者的需要生產出相應的對象。
最後,看看cocos2d-x創始人王哲對於什麼是單例的看法:
“這麼說吧, 我設計成單例基本就一種抽象情況:獨佔性資源。比如某個硬體IO (如CCTouchDispatcher, CCAccelerometer),比如公用的緩衝區(CCTextureCache, CCUserDefault)。後來有人抱怨單例類太多,想銷毀整個cocos2d instance再重建很麻煩,所以小明和riq就把大量單例類放到CCDirector裡面管理。”
摘自:http://www.zilongshanren.com/cocos2d-x-design-pattern-singleton1/comment-page-1/#comment-20
歡迎關注關東升新浪微博@tony_關東升。
關注智捷課堂公用平台,瞭解最新技術文章、圖書、教程資訊
更多精品iOS、Cocos、移動設計課程請關注智捷課堂官方網站:http://www.zhijieketang.com
智捷課堂論壇網站:http://51work6.com/forum.php
cocos2d-x設計模式發掘之一:單例模式