Cocos2D-X 设计模式:二段构建模式

前端之家收集整理的这篇文章主要介绍了Cocos2D-X 设计模式:二段构建模式前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
所谓二段构建,就是指创建对象时不是直接通过构建函数来分配内存并完成初
始化操作。取而代之的是,构造函数只负责分配内存,而初始化的工作则由一
组合起来,完成最终对象的构建。因为在《Cocoa设计模式》一书中,把此
惯用法称之为“Two Stage Creation”,即“二段构建”。

Cocos2D-X 3.0以后的二段构造更纯粹了,它把所有类的构造函数、析构函数以及init方法全部变成protected方法。强制开发者只能通过create工厂方法来创建对象。这样做的好处有两个:

一、可以更好地配合Cocos2D-X内部提供的基于引用计数的内存管理模型,因为create方法按照约定会返回一个autorelease对象。
二、create工厂方法可以有参数,例如createWithFileName,createWithSpriteFrameName,这样比构造函数更具有可读性。因为构造函数的重载可读性真的很差。这一点,我特别喜欢Objective-C的中缀表达式语法,虽然要多写一些字符,但是可读性真的很棒。

1.应用场景:

二段构建在cocos2d-x里面随处可见,自从3.0版本以后,所有的二段构建方法的签名都改成create了。这样做的好处是一方面统一接口,方便记忆,另一方面是以前的类似Cocoa的命名规范不适用c++,容易引起歧义。下面以Sprite为类,来具体阐述二段构建的过程,请看下列代码

Sprite* Sprite::create()
{
    Sprite *sprite = new (std::nothrow) Sprite();
    if (sprite && sprite->init())
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}

如上面代码中的注释所示,创建一个sprite明显被分为两个步骤:1.使用new来创建内存;2.使用initXXX方法来完成初始化。

因为Sprite的构造函数也有初始化的功能,所以,我们再来看看Sprite的构建函数实现:

Sprite::Sprite(void)
: _shouldBeHidden(false),_texture(nullptr),_insideBounds(true)
{
}

很明显,这个构建函数所做的初始化工作非常有限,仅仅是在初始化列表里面初始化了m_pobTexture和m_bShouldBeHidden两个变量。实际的初始化工作大部分都放在initXXX系列方法中,大家可以动手去查看源代码

2.分析为什么要使用此模式?

这种二段构建对于C++程序员来说,其实有点别扭。因为c++的构造函数在设计之初就是用来分配内存+初始化对象的。如果再搞个二段构建,实则是多此一举。但是,在objective-c里面是没有构造函数这一说的,所以,在Cocoa的编程世界里,二段构建被广泛采用。而cocos2d-x当初是从cocos2d-iphone移植过来了,为了保持最大限度的代码一致性,所以保留了这种二段构建方式。这样可以方便移植cocos2d-iphone的游戏,同时也方便cocos2d-iphone的程序员快速上手cocos2d-x。

不过在后来,由于c++天生不具备oc那种可以指定每一个参数的名称的能力,所以,cocos2d-x的设计者决定使用c++的函数重载来解决这个问题。这也是后来为什么2.0版本以后,都使用create函数的重载版本了。

虽然接口签名改掉了,但是本质并没有变化,还是使用的二段构建。二段构建并没有什么不好,只是更加突出了对象需要初始化。在某种程度上也可以说是一种设计强化。因为忘记初始化是一切莫名其妙的bug的罪魁祸首。同时,二段构建出来的对象都是autorelease的对象,而autorelease对象是使用引用计数来管理内存的。客户端程序员在使用此接口创建对象的时候,无需关心具体实现细节,只要知道使用create方法可以创建并初始化一个自动释放内存的对象即可。

在一点,在《Effective Java》一书中,也有提到。为每一个类提供一个静态工厂方法来代替构造函数,它有以下三个优点:

与构造函数不同,静态方法有名字,而构造函数只能通过参数重载。

它每次被调用的时候,不一定都创建一个新的对象。比如boolean.valueof(boolean)。

它还可以返回原类型的子类型对象。

因此,使用二段构建的原因有如下几点:- 兼容性、历史遗留原因。(这也再次印证了一句话,一切系统都是遗留系统)

二段构建有其自身独有的优势。

构造函数执行期间是不能调用virtual函数的(即使调用了virtual,编译器也会用静态调用机制而不是virtual机制,详见Effective C++条款9),如果不用二段建构方式,在基类的构造函数里就不能调用virtual函数实现子类需要定制化的功能,比如当需要采用模板方法这样的设计模式做初始化的时候。但如果使用二段建构,就可以把这部分放在init()里,实现了初始化时使用模板方法的方式。构造函数里无法通过virtual函数实现虚函数机制,但init函数调用的时候,就可以调用virtual函数了(感谢nichos)

如果在构造函数调用可能异常退出函数,那么当异常发生,函数调用栈马上弹出,直到找到try cathch为止。也就是说分配出的内存来不急释放(在构造函数里发生异常,甚至连分配出的内存指针都拿不到),函数执行就中止了。进行两段构造可以提供一个进行try catch的机会,Symbian的两段构造+清除栈的处理方式比这里提到的策略安全的多。(感谢omega)

3.使用此模式的优缺点是什么?

优点:

显示分开内存分配和初始化阶段,让初始化地位突出。因为程序员一般不会忘记分配内存,但却常常忽略初始化的作用。

见上面分析《Effective Java》的第1条:“为每一个类提供一个静态工厂方法来代替构造函数

除了完成对象构建,还可以管理对象内存。

缺点:

1.不如直接使用构造函数来得直白、明了,违反直觉,但这个是相对的。

4.此模式的定义及一般实现

定义:将一个对象的构建分为两个步骤来进行:1.分配内存 2.初始化它的一般实现如下:

class Test {
public:
    //静态工厂方法
    static Test* create()
    {
        Test *pTest = new Test;
        if (pTest && pTest->init()) {
            //这里还可以做其它操作,比如cocos2d-x里面管理内存
            return pTest;
        }
        return NULL;
    }
    //
    Test()
    {
        //在构造函数初始化列表里面初始化一些成员变量
    }
    bool init(){
        //这里初始化对象成员
        return true;
    }
private:
    //这里定义数据成员

};

5.在游戏开发中如何运用此模式

这个也非常简单,就是今后在使用cocos2d-x的时候,如果你继承Sprite实现自定义的精灵,你也需要按照“二段构建”的方式,为你的类提供一个静态工厂方法,同时编写相应的初始化方法。当然,命名规范最好和cocos2d-x统一,即静态工厂方法为create,而初始化方法为initXXXX。

6.此模式经常与哪些模式配合使用

由于此模式在GoF的设计模式中并未出现,所以暂时不讨论与其它模式的关系。

最后看看cocos2d-x创始人王哲对于为什么要设计成二段构建的看法:

“其实我们设计二段构造时首先考虑其优势而非兼容cocos2d-iphone. 初始化时会遇到图片资源不存在等异常,而C++构造函数无返回值,只能用try-catch来处理异常,启用try-catch会使编译后二进制文件大不少,故需要init返回bool值。Symbian,Bada SDK,objc的alloc + init也都是二阶段构造”。

猜你在找的Cocos2d-x相关文章