首先明确一个问题,什么是管理者模式,管理类是用来管理一组相关对象的类,他提供了访问对象的接口,如果这么说比较抽象的话,我们来看下cocos2dx中都有哪些类是管理类你就会很明白了,例如TextureCache, SpriteFrameCache, AnimationCache,这些类都是管理类。就拿SpriteFrame来说,这个类管理了对象spriteframe,我们通过提供一个键来获得对应的值,像AnimationCache,TextureCache不都是这样吗,用一个键来获取对应的值。所以这些类都叫做管理类,因为他们管理着一组相关的对象。之所以使用管理者模式一个是因为为访问相关对象提供了统一的接口,另一个就是缓存游戏用到的资源,提高游戏的性能,以上的三个类不正是这样的作用吗,以下是实现这个模式的代码。
1 | #ifndef _MANAGER_PATTERN_H_ |
2 | #define _MANAGER_PATTERN_H_ |
10 | static ManagerPattern * getInstance() |
14 | m_manager = new ManagerPattern(); |
15 | m_dictionary = new Dictionary(); |
16 | m_dictionary->retain(); |
20 | static void freeInstance() |
22 | CC_SAFE_DELETE(m_manager); |
23 | CC_SAFE_RELEASE_NULL(m_dictionary); |
25 | void registeInstance( const std::string& key,CCObject *obj) |
27 | m_dictionary->setObject(obj,key); |
29 | CCObject* getObject( const std::string& key) |
31 | return m_dictionary->objectForKey(key); |
35 | static ManagerPattern * m_manager; |
36 | static Dictionary * m_dictionary; |
39 | ManagerPattern * ManagerPattern::m_manager = NULL; |
以上的管理者类使用了单例,内部有一个Dictionary,用来以键值对的方式存储我们的对象,访问的时候通过一个键,返回对应的对象值,在cocos中我们只需要明白那些缓存类其实就是管理者模式就可以了,我们创建SpriteFrameCache的时候之所以提供了一个plist文件就是因为它要使用这个文件的信息来创建对象,然后根据sprite的名称来返回创建好的对象。我认为在cocos中使用管理者模式最重要的就是提高了效率,提前缓存了我们使用的资源。
| SpriteFrameCache::getInstance()->addSpriteFramesWithFile( "1.plist" ); |
4 | auto spriteFrame = SpriteFrameCache::getInstance()->getSpriteFrameByName( "xiaota" ); |
6 | auto callback = [](Ref *){}; |
7 | TextureCache::getInstance()->addImageAsync( "xiaota.png" ,callback); |
| auto texture2d = TextureCache::getInstance()->getTextureForKey( "xiaota.png" ); |
| AnimationCache::getInstance()->addAnimation(Animation::create(), "xiaota" ); |
| AnimationCache::getInstance()->getAnimation( "xiaota" ); |
仍然先来说明一下何为外观模式,一个复杂的系统包含很多子系统,为了使用这个复杂的系统,我们定义一个统一的接口来使用这个复杂的系统。当用户操作的时候只要调用我们提供的这个接口就好了,至于底层的这个复杂的系统,用户不必关系是如何工作的。这里列举一个网上的例子,编译系统是一个复杂的系统,包括什么词法分析,语法分析,语义分析等等,用户在编译程序的时候不需要了解这个复杂的系统到底是怎么编译的,只要使用统一的接口编译就可以了,代码如下。
10 | cout << "scan code" << endl; |
20 | cout << "parse code" << endl; |
30 | cout << "generate code" << endl; |
41 | cout << "compile program…" << endl; |
46 | CCodeGenerator generator; |
一个相似的例子。
2 | public void freeze() { ... } |
3 | public void jump( long position) { ... } |
4 | public void execute() { ... } |
7 | public void load( long position, byte[] data) { ... } |
10 | public byte[] read( long lba, int size) { ... } |
15 | private Memory memory; |
16 | private HardDrive hardDrive; |
19 | this .memory = new Memory(); |
20 | this .hardDrive = new HardDrive(); |
22 | public void startComputer() { |
24 | memory.load(BOOT_ADDRESS, hardDrive.read(BOOT_SECTOR, SECTOR_SIZE)); |
25 | cpu.jump(BOOT_ADDRESS); |
31 | public static void main(String[] args) { |
32 | Computer facade = new Computer(); |
33 | facade.startComputer(); |
从上边的例子中可以看出来,这个外观模式不是封装的一个对象,而是封装了一组对象,我们通过它的接口来实现功能,而内部它调用了各个对象的功能模块。这样做的好处不用说也明白了吧,就是简化了用户的操作,当然用户如果想要调用底层的代码也是可以的,这样可以获得更灵活的功能。在cocos2dx中一个外观模式的例子就是SimpleAudioEngine,我们播放声音就是调用这个类的接口来完成的,而底层它是使用了CDSoundEngine、CDAudioManage这俩个类的相关管理声音的函数。我们知道SimpleAudioEngine是没有函数提供给我们循环播放音效的,但是我们可以使用底层的CDAudioManage来实现这个功能,这就是所谓的灵活性吧。
设计模式——防御式编程模式
防御式编程模式,这个是在浏览其他博客的时候看到的,这里大体说一下它的意思,关于这方面的文章自行百度吧。其实防御式编程模式并不是一种编程模式,只是说我们写代码的时候为了要保证程序的健壮性要采取一定的防御措施,我们写代码通常都是带有一定的假定的,设想一下如果用户输入了非法的值,我们的假定就会打破,程序就会出现bug,所以为了防止程序出错,我们需要采取一定的措施来避免这种不确定的操作导致的bug,这个就叫做防御式编程模式。
这个在cocos的代码中经常看到,比如和do...while配合的CC_BREAK_IF,如果某一个对象没有初始化成功就break这个循环,这时候init的返回值是false,这样我们就知道了程序出错了。但是3.0的版本在init函数中却没有这么写,我想也许是这么写太麻烦了吧,也就是说这种编程模式会造成代码的臃肿。还有引擎为我们提供的以下的一些宏,我们在编程的时候要尽量使用这些宏来保证程序的健壮性。
1 | #define CC_SAFE_DELETE(p) do { if(p) { delete (p); (p) = 0; } } while(0) |
2 | #define CC_SAFE_DELETE_ARRAY(p) do { if(p) { delete[] (p); (p) = 0; } } while(0) |
3 | #define CC_SAFE_FREE(p) do { if(p) { free(p); (p) = 0; } } while(0) |
4 | #define CC_SAFE_RELEASE(p) do { if(p) { (p)->release(); } } while(0) |
5 | #define CC_SAFE_RELEASE_NULL(p) do { if(p) { (p)->release(); (p) = 0; } } while(0) |
6 | #define CC_SAFE_RETAIN(p) do { if(p) { (p)->retain(); } } while(0) |
所以这种编程模式其实就是考虑程序可能存在的一些bug而采取的一种措施,比如检测函数传递进来的参数,使用符号常量来定义一些文件名或者是数字,函数最好有返回值,这样可以容易发现bug。恩,就是这些吧,我觉得这种编程模式也是一种习惯,要想养成良好的编程习惯平时就注意一下这些小的细节问题。