【木头Cocos2d-x 018】面向接口编程01: 向她表白之实现函数回调

本文详细介绍了如何在Cocos2d-x中通过面向接口编程实现函数回调,针对Java背景开发者提供了从Java移植到C++的具体步骤与技巧,包括消息派发、接口使用方式及实例应用等,旨在帮助开发者更高效地理解和实践Cocos2d-x中的面向接口编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【在Cocos2d-x中面向接口编程01】向她表白之实现函数回调

首先,我要郑重地声明一件事情:我现在很尿急,但是厕所有人~

嗯,然后,我本来是一名Java程序员,由于Cocos2d-x太吸引人,我就硬着头皮学,可是C++Java那看似差不多,实际又确实差很多的纠结,让我变得...很尿急~

旁白:可以少说一些废话么?

最近我在试着写一个小游戏,叫做《粒子格斗》。

旁白:你已经说过了。。。

然后,我自己写了一个消息派发的功能,因为当时我并不知道Cocos2d-x已经集成了这个功能。我写Java习惯了,所以我按照Java的思维习惯实现了这个消息派发。如果对消息派发不熟悉或者完全不懂的朋友,那个,还是百度一下会好一些~

旁白:等等~!你不是应该介绍实现函数回调吗?

哦,对,我应该介绍实现函数回调的,但是如果你和我一样,是Java出身的,并且Java功底也不是很深厚,对于掌握新语言不太迅速的话,那么,请继续往下看。

如果你对C++已经是比较熟悉了,那,可以忽略这篇文章。

笨木头花心贡献,啥?花心?不呢,是用心~

转载请注明,原文地址
http://blog.youkuaiyun.com/musicvs/article/details/8261147

正文:

1.Java移植过来的消息派发

按照Java的思路,实现如下:

a.首先要实现消息派发的核心类,用于订阅、删除以及发布消息,这个类只需用实现3个函数:

/* 订阅消息 */
addListener(I_MessageDispath msgListener,  const char* sMsgType);

/* 删除订阅的消息 */
delListener(I_MessageDispath msgListener,  const char* sMsgType);

/* 发布消息 */
dispatchMsg(const char* sMsgType, CCObject* pData);


b.然后就要实现一个接口,一个代表消息订阅者的接口(这很美妙,我太喜欢接口了,它让我的代码之间的耦合度减小了很多很多~!),我们命名为I_MessageDispatch

class I_MessageDispatch : public CCObject {
public:    
    void onRecv(CCObject* pData);
};


c.详细的就不多解释了,我们再用一个CCDictionary保存不同类型的订阅者对象,比较完整的头文件内容如下:

class MessageDispatch : public CCObject {
public:
    static MessageDispatch* sharedMessageDispatch();

    /* 订阅消息 */
    addListener(I_MessageDispath msgListener,  const char* sMsgType);

    /* 删除订阅的消息 */
    delListener(I_MessageDispath msgListener,  const char* sMsgType);

    /* 发布消息 */
    dispatchMsg(const char* sMsgType, CCObject* pData);

private:
    /* CCDictionary<CCArray<I_MessageDispatch*>, const char*> */
    CCDictionary* mListenerDict;
};


d.然后就是一些细节的处理,好,不多说。就是字典里保存了消息类型和消息订阅者列表的对应关系。添加订阅的时候,就把实现了I_MessageDispatch接口的对象添加到对应的列表里(删除同理),然后发布消息的时候,取出要发布的消息类型对应的订阅者列表,对列表中的每个对象都调用一次onRecv函数,就可以了。

旁白:你不是说不多说么?==我感觉你说了很多

2.看起来它工作得很好

看起来,这个消息派发类工作得不错,所有功能都能正常运行。可是,问题来了。

C++中是没有接口的说法的,它只有多继承,我太怕多继承了,我可给它整晕了好多次。首先它有一个弱点,二义性,额,我就不解释了,专业解释请百度一下(旁白:你压根儿就解释不了吧...)。比如我有这样一个类:

class ClientFight : public CCLayer {
public:
    bool initWithLayer(CCLayer* mLayer);
    static ClientFight* createWithScene(CCLayer* mLayer);
#endif

嗯,看起来没有什么问题,但是,如果我这个类想订阅消息,怎么办?很简单啊,让它继承I_Messagedispatch接口,然后实现onRecv函数,然后订阅消息,就OK了,这是我在写Java时最容易想到的方法。

class ClientFight : public CCLayer, public I_MessageDispatch {
public:
    bool initWithLayer(CCLayer* mLayer);
  static ClientFight* createWithScene(CCLayer* mLayer);

    virtual void onRecv(CCObject* pData);
#endif


3.二义性,烦死你

来看看这样一个情景,某个类创建了ClientFight对象,并且保持了它的引用,当我要释放引用的时候,很自然要调用release函数。于是,编译器会告诉你,ClientFightrelease函数调用不明确。

原因呢?ClientFight继承了CCLayer,CCLayer继承了CCObjectI_MessageDispatch也继承了CCObject

于是,它们两个都有release函数,而ClientFight作为它们的子类,不知道该继承谁的release才好,所以编译器晕了,我也晕了。

要解决这个问题很简单,本来是这样调用的:mClientFight->release();

现在改为:mClientFight->CCLayer::release();

OK,这样编译器就不会晕了。但我还是晕啊,这太麻烦了~!这只是其中一个小问题,以后还会遇到更多问题,对于Java出身的人来说,我暂时无法接受这么麻烦的编码。

4.发现使用接口的新方式

我写的那个消息派发还不止这点故障,还有更多的故障,我就不说了。

偶然的机会,我百度了一下:Cocos2d-x观察者。

然后被我发现了子龙山人的博客,他介绍了Cocos2d-x的观察者模式,正好就是说的消息派发,木了个头的,原来已经有这个功能了,我还干嘛写得这么辛苦?

旁白:你废话多,所以总是干些多余的事情==

这个消息派发的功能大家可以看看这个类:CCNotificationCenter

或者看看大牛的博客:

http://www.zilongshanren.com/cocos2d-x-design-pattern-6-observer/

http://www.xinze.me/cocos2d-x-ccnotificationcenter/

我稍微拜读了一下Cocos2d-x的源码,发了一个很奇妙的地方,是一种“另类”的接口使用方式。

CCNotificationCenter在添加订阅者的时候,不需要订阅者实现某个接口,直接添加就可以了。

CCNotificationCenter会自动新建一个对象,这个对象是CCNotificationObserver,其实这就可以当做是一个接口,只不过订阅者不是实现这个接口,而是直接绑定到这个接口身上。按照我朋友的一个说法,这有点像适配器模式,也许就是适配器模式。

void CCNotificationCenter::addObserver(CCObject *target, 
                                       SEL_CallFuncO selector,
                                       const char *name,
                                       CCObject *obj)
{
    if (this->observerExisted(target, name))
        return;
    
    CCNotificationObserver *observer = new CCNotificationObserver(target, selector, name, obj);
    if (!observer)
        return;
    
    observer->autorelease();
    m_observers->addObject(observer);
}


怎么样?这样我们就可以避免一个对象为了订阅消息而不得不使用多继承了~

5.实例:向她表白

我们先来练练手,熟悉一下C++的函数参数的传递,再次声明,已经熟悉C++的朋友,直接忽略我。

旁白:我们早就忽略你了。。。

我们来实现一个经典的事情:我不敢向喜欢的女生表达,所以我想让我的朋友帮我这个忙。

这个类很简单:

#ifndef __TELL_LIKE_HER_H__
#define __TELL_LIKE_HER_H__

#include "cocos2d.h"

USING_NS_CC;

typedef void (CCObject::*SEL_CallFuncL) (CCObject*);
#define callfuncL_selector(_SELECTOR) (SEL_CallFuncL)(&_SELECTOR)

/* 这是一个不太像接口的接口 */
class I_LikeHer : public CCObject {
public:
    I_LikeHer(CCObject* targer, SEL_CallFuncL selector);

    void performSelector();

private:
    SEL_CallFuncL mSelector;
    CCObject* mTargert;
};

/* 这是我的朋友,他会帮我表达我的心意 */
class TellLikeHer : public CCObject {
public:
    static TellLikeHer* sharedTellLikeHer();

    /* 设置要表白的主角~ */
    void setTheActor(CCObject* targer, SEL_CallFuncL selector);

    /* 她就在这里,TellLikeHer将代替主角传达心意 */
    void sheIsHere();

private:
    static TellLikeHer* mTellLikeHer;

    I_LikeHer* mLikerHer;
};
#endif


要实现函数回调,我们需要两样东西:

typedefvoid(CCObject::*SEL_CallFuncL)(CCObject*);

#definecallfuncL_selector(_SELECTOR)(SEL_CallFuncL)(&_SELECTOR)

定义一个SEL_CallFuncL类型,以及一个传递函数参数的宏,我不太善于解释这个,我是依葫芦画瓢仿照源码写的,所以我选择不解释。

我们看到有一个I_LikeHer类,这个就是相当于一个接口,TellLikeHer类只保存这种接口的类,所以,我们依旧是面向接口编程,减少了耦合度。

我们来看看setTheActor函数(函数名取得不好,不要灭了我。。。):

void TellLikeHer::setTheActor( CCObject* targer, SEL_CallFuncL selector ) {
    mLikerHer = new I_LikeHer(targer, selector);
}


很简单,外部传进来的可以是任意的对象类型(当然,必须是继承了CCObject的),TellLikeHer类根据就不需要关心谁传进来了,因为它会创建一个I_LikeHer对象,把传进来的对象包装起来。

噗,听起来又有点像装饰者模式?是蛮像的。

再来看看sheIsHere函数:

void TellLikeHer::sheIsHere() {
    mLikerHer->performSelector();
}


这确实是在面向接口编程,因为我们只需要调用I_LikeHer接口的performSelector函数,调用了这个函数之后,监听者就会被回调,是的,我们帮他传达了心意了,并且告诉了他结果。

void I_LikeHer::performSelector() {
    (mTargert->*mSelector)(NULL);
}


这个是什么东西?它就是我们的主题:函数的回调。

这有点混乱,我明白。(旁白:你真心觉得只有一点点混乱?)我们来画图理解:

这很好地解决了二义性的问题。

也许是我的经验太少,对接口的使用了解不深入,所以总以为面向接口编程,就是定义一个接口,让其他类来实现。

Java里,我暂时没有遇到什么大问题。

不过在Cocos2d-x中刚好暴露了这种方式的缺陷。

好吧,对我来说,这是一次不错的收获。因为二义性的问题,让我认识了Cocos2d-x的消息派发(CCNotificationCenter)以及一种很不错接口使用方式。

又不过,这种接口的使用方式也不见得是万能的,如果一个接口要实现多个函数,那,又该怎么做呢?于是,我得拿出一本C++的教程,研究一下C++关于接口方面的知识了。

旁白:噗,说了等于没说。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值