设计模式在程序设计中会经常用到,也许你从来没有留意过设计模式,其实你却一直在使用设计模式!cocos2dx中有不少的设计模式,所以从本篇博客开始探讨一下cocos2dx中的设计模式,看看引擎都使用了哪些设计模式,我们今后写代码要怎样使用某种设计模式。本人菜鸟一枚,如果有任何错误还请留言指教,互相探讨。
大家最熟悉的是单例设计模式了吧,在cocos2dx中单例真是不少啊,我们的大导演不就是单例吗,单例设计模式我之前写过一篇博客,这里就不多说了。第二个设计模式是观察者模式,什么是观察者模式,如何实现观察者模式。就是被观察者含有一个数组,里边存放了所有观察者的引用,在被观察者的状态发生改变的时候,通过调用观察者的函数来通知观察者,实现了信息的传递。之前也有一篇博客写了cocos2dx中用来实现观察者模式的事件监听器NotificationCenter,大家可以看看。本篇博客探讨一下二段构建模式,首先第一个问题是什么是二段构建模式。
大家都知道在c++中我们一般在构造函数中为对象分配内存空间然后初始化成员变量,比如我们调用了new某个东西,那么在堆上会先为对象分配内存空间,然后调用构造函数,在构造函数中完成一些初始化的工作。而二段构建模式就是将内存空间的分配和初始化分开来完成,然后调用一个静态方法来返回这个对象。就拿cocos2dx中的Sprite类来说吧,当我们调用Sprite::create()的时候内部先使用new来分配内存空间,然后调用init方法来初始化一些变量的设置。所以cocos2dx中的二段构建模式就是将new分配内存空间和init初始化内容分开来处理,而不是c++传统的做法在构造函数中初始化变量。
1 | Sprite* Sprite::create() |
4 | Sprite *sprite = new Sprite(); |
6 | if (sprite && sprite->init()) |
12 | CC_SAFE_DELETE(sprite); |
上边就是使用二段构建模式的过程,Sprite首先调用new来分配内存空间,然后调用init函数来完成初始化的工作,顺带还做了内存管理的工作,最后返回初始化好的对象。所以看了Sprite的create方法的实现,我们也知道了应该怎么使用这个二段构建模式了吧。
第二个问题是为什么要这么用,对于c++程序员来说初始化工作不都是在构造函数中完成的吗,cocos中为何要这么做呢?这里引述一下王哲的话:“其实我们设计二段构造时首先考虑其优势而非兼容cocos2d-iphone. 初始化时会遇到图片资源不存在等异常,而C++构造函数无返回值,只能用try-catch来处理异常,启用try-catch会使编译后二进制文件大不少,故需要init返回bool值。Symbian, Bada SDK,objc的alloc + init也都是二阶段构造”。现在大家明白了吧,兼容cocos2d-iphone是一个原因,另一个重要的原因是构造函数没有返回值啊,如果加载资源图片的时候不存在怎么办,所以初始化的工作写在init函数中,这个函数返回的bool值用来判断是否初始化成功。使用这种方法还可以强化设计,想想自己写代码的时候是不是因为没有初始化某个成员变量导致了bug,这样做就是提醒你记得要在init中初始化成员变量。通过create静态函数返回的这个对象也实现了cocos2dx中的内存管理,就不用我们自己麻烦了。还有一个原因是在c++的构造函数中是不能调用虚函数的,为了调用虚函数来完成一些功能就要写在init函数中。
以上就是二段构建模式的说明了,在我们写cocos程序的时候其实不知不觉就已经在使用这个构建模式了,想一下我们一个类继承了Layer,然后使用了宏CREATE_FUNC(),这不就是create静态方法吗,在init函数中完成了初始化,整个过程就是在用这种设计模式!
设计模式——工厂模式
cocos2dx中也有工厂模式,何为工厂模式,顾名思义就是用来产生产品的,工厂就是用来创建其他类对象的类,我们把这个创建其他类对象的类叫做工厂类,而这些被创建的对象叫做产品,所以这种模式才叫做工厂模式,是不是很形象。我们从纯c++的角度来看一下如何使用工厂模式。工厂模式又分为简单工厂模式、工厂方法模式、抽象工厂模式,先来看一下简单工厂模式如何实现。
简单工厂模式:当在程序中创建对象的时候少不了new,有时候new会很多,又分布在程序的不同地方,管理起来很不方便,这个时候需要一个工厂类,专门负责对象的创建和释放,将对象的这种操作统一在一起,同时工厂类向外部提供了创建对象的接口,而对对象的使用则和这个工厂类毫无关系。
18 | virtual void show()=0; |
21 | class ProductA : public Product |
30 | class ProductB : public Product |
44 | Product * createProduct(ProductType type) |
49 | return new ProductA(); |
51 | return new ProductB(); |
5 | Factory * factory = new Factory(); |
7 | ProductA * pa = (ProductA *)factory->createProduct(typeA); |
10 | ProductB * pb = (ProductB *)factory->createProduct(typeB); |
工厂方法模式:是为了解决简单工厂模式的弊端存在的,简单工厂模式的扩展性不好,比如我们有了第三个产品ProductC,我们需要工厂为我们产生这个对象,怎么办,需要修改工厂类中的创建对象的函数,也就是switch结构,还有就是枚举处也要进行修改,而这种修改会带来不少的弊端,所以我们就有了工厂方法模式。这个模式将Factory设计为抽象类,其中包含子类必须实现的方法,而对产品的具体创建则放到Factory的子类中去完成。这个时候如果有一个产品C,我们就创建一个工厂类FactoryC,专门用来产生产品C,就不需要改动其他地方的代码了。
12 | virtual void show()=0; |
15 | class ProductA : public Product |
24 | class ProductB : public Product |
38 | virtual Product * createProduct()=0; |
42 | class FactoryA : public Factory |
45 | Product * createProduct() |
47 | return new ProductA(); |
51 | class FactoryB : public Factory |
54 | Product * createProduct() |
56 | return new ProductB(); |
6 | FactoryA * factoryA = new FactoryA(); |
7 | Product * pa = factoryA->createProduct(); |
10 | FactoryB * factoryB = new FactoryB(); |
11 | Product * pb = factoryB->createProduct(); |
抽象工厂模式:现在我们的产品类都是继承自Product的,如果有一个产品不是Product的子类怎么办呢?这个时候就用到了抽象工厂模式,这个模式是工厂方法模式的叠加,其他的东西类似,看下代码就清楚了。
12 | virtual void show()=0; |
15 | class ProductA1 : public ProductA |
24 | class ProductA2 : public ProductA |
36 | virtual void show()=0; |
39 | class ProductB1 : public ProductB |
47 | class ProductB2 : public ProductB |
60 | virtual ProductA * createProductA()=0; |
61 | virtual ProductB * createProductB()=0; |
65 | class FactoryA : public Factory |
68 | ProductA * createProductA() |
70 | return new ProductA1(); |
72 | ProductB * createProductB() |
74 | return new ProductB1(); |
78 | class FactoryB : public Factory |
81 | ProductA * createProductA() |
83 | return new ProductA2(); |
85 | ProductB * createProductB() |
87 | return new ProductB2(); |
以上是对工厂模式的说明,下面看看工厂模式在cocos2dx中的应用,引用cocos2d-x高级开发教程一书中的话:“工厂方法是程序设计中一个经典的设计模式,指的是基类中只定义创建对象的接口,将实际的实现推迟到子类中。在这里,我们将它稍加推广,泛指一切生成并返回一个对象的静态函数”。一切生成并返回一个对象的静态函数就是一个工厂方法,这样的话,cocos2dx中是不是有很多这样的方法?比如创建场景的createScene函数,创建多数对象的create函数,一个经典的工厂方法如同这样:
1 | Sprite* factoryMethod() |
3 | Sprite* ret = new Sprite(); |
在我们自己的程序中使用工厂模式的应用场景可以是这样:我们要创建很多的子弹,如果使用Sprite的create方法每次都会分配内存,子弹销毁的时候释放内存,这样的创建方法效率不高,如果我们使用工厂方法来完成这件事情,自己的工厂方法立面维护一个容器,容器里存放被销毁的子弹,需要新的子弹的时候,从容器中拿出来。根据子弹的类型,更换纹理,重置位置,重置飞行速度和方向,然后发射出去。如果容器中没有被销毁的子弹,就初始化一个,这样的话就不用每次new、delete了,内存中的子弹数量是一定的,可以提高程序的效率。
首先明确一个问题,什么是管理者模式,管理类是用来管理一组相关对象的类,他提供了访问对象的接口,如果这么说比较抽象的话,我们来看下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。恩,就是这些吧,我觉得这种编程模式也是一种习惯,要想养成良好的编程习惯平时就注意一下这些小的细节问题。
转载自http://www.zaojiahua.com/