回调机制
在回调机制中,存在三个角色:使用者、实现者、约定的接口,它们之间的三者关系如下:
回调机制 最大的作用就是解耦,通过 约定的接口,使用者 和 实现者 之间可以实现解耦;
- 使用者 不用关心如何实现相关的功能,只需要调用 约定的接口 就行了;
- 实现者 不用关心如何被调用,只需要按照 约定的接口,实现功能就行了;
- 约定的接口 是 使用者 和 实现者 之间联系的纽带;
回调函数的应用非常的广泛。通常,我们需要一个统一得接口来实现不同内容的时候,用回调函数来实现就非常合适;许多窗口系统就是使用回调函数来连接多个动作,例如:拖拽鼠标和单击按钮来指定调用用户程序中的某个特定函数。
回调函数的机制优点类似与设计模式中 消费者–使用者模式;
C/C++ 常见的回调形式
Callback形式
C 语言中,回调机制的实现是把 约定的接口 的形参设置为函数指针来实现的。下面一个例子用来说明 Callback
的调用形式(例子):这个例子主要模拟的是下载文件;
#ifndef __cplusplus // g++编译器会定义 __cplusplus
#error You are not Currently using a C++ compiler
#endif
#include <iostream>
using namespace std;
// Define Function Pointer TYpes
typedef void (*DownloadCallback)(const char *_pURL, bool _ok);
// Conventional Interface
void DownLoadFile(const char *pURL, DownloadCallback callback)
{
cout << "downloading ..." << pURL << " " << endl;
callback(pURL, true);
}
// The Function being called back
void onDownloadFinished(const char* _pURL, bool _ok)
{
cout << "onDownloadFinished ..." << _pURL << " status:" << _ok << endl;
}
int main()
{
DownLoadFile("http://www.baidu.com", onDownloadFinished);
system("pause");
return 0;
}
在上述例子中,各个函数承担的角色分别如下:
- 调用者: 主函数;
- 约定的接口:
void DownLoadFile(const char *pURL, DownloadCallback callback)
; - 实现者:
void onDownloadFinished(const char* _pURL, bool _ok)
;
下面我们来看 Callback
应用的另一个例子:在一个链表中,查找指定数据的节点。
链表的数据结构如下:
typedef struct list {
void *value_address;
struct list *next;
}Node;
假设,要比较的数据是 int
类型,我们可以这样写:
Node *Search_List_int(Node *_node, const int _value)
{
while(!_node) {
if(_value == *(int *)_node->value_address) break;
_node = _node->next;
}
return _node;
}
假设,要比较的数据是字符串,我们可以这样写:
Node *Search_List_str(Node *_node, const char *desired_str)
{
while(!_node) {
const char *str = _node->value_address;
if(!strcmp(str, desired_str)) break;
_node = _node->next;
}
return _node;
}
函数 Node *Search_List_int()
与 Node *Search_List_str
实现的功能都是对数据进行比较,唯一不同的是比较的数据类型不一样。如果利用回调机制如何实现一个与类型无关的比较函数呢?
首先,我们确定约定的接口:
typedef int (*COMPARE)(void const *, void const *);
// Conventional Interface
Node *Search_List1(Node *_node, COMPARE _compare, void const *desired_value)
{
int ret;
while (!_node) {
ret = _compare((_node->value_address), desired_value);
if(!ret) break;
_node = _node->next;
}
return _node;
}
再次,我们实现 实现者:
// The function being called back: int type comparison
int int_compare(void const *a, void const *b)
{
if(*(int *)a == *(int *)b) return 0;
return -1;
}
最后使用形式如下:
desired_node = Search_List(root, int_compare, &desired_int_value);
desired_node = Search_List(root, strcmp, "abcdefg"); // 有可能会爆出警告,因为 strcmp 的参数类型为 "const char *" 而不是 "void const *"
Sink 的调用形式
Sink 的调用形式就是以 C++ 类的形式来实现回调功能。还是以下载文件为例:
#ifndef __cplusplus
#error You should use c++ compiler
#endif
#include <iostream>
// Conventional Interface
class IDownloadSink
{
public:
virtual void OnDownloadFinished(const char *pURL,bool bOK) = 0;
};
// Interface user
class CMyDownloader
{
private:
IDownloadSink *m_pSink; // interface object
public:
CMyDownloader (IDownloadSink *pSink)
:m_pSink(pSink) { }
// be using
void DownloadFile(const char* pURL)
{
std::cout<<"downloading..."<<pURL<<""<<std::endl;
if(m_pSink!=NULL) m_pSink->OnDownloadFinished(pURL,true);
}
};
// The implementer of the interface
class CMyFile:public IDownloadSink
{
public:
virtual void OnDownloadFinished(const char *pURL,bool bOK)
{
std::cout<<"onDownloadFinished..."<<pURL<<" status:"<<bOK<<std::endl;
}
public:
// Implement a complete function
void download()
{
CMyDownloader downloader(this);
downloader.DownloadFile("www.baidu.com");
}
};
void main()
{
CMyFile *file = new CMyFile();
file->download();
system("pause");
}
在上述代码中:
- 约定的接口:
IDownloadSink
; - 使用者:
CMyDownloader
; - 实现者:
CMyFile
使用者 和 实现者 只需要各自的实现。
Delegate 方式
class CDownloadDelegateBase
{
public:
virtual void Fire(const char* pURL, bool bOK) = 0;
};
template<typename O, typename T>
class CDownloadDelegate: public CDownloadDelegateBase
{
typedef void (T::*Fun)(const char*, bool);
public:
CDownloadDelegate(O* pObj = NULL, Fun pFun = NULL)
:m_pFun(pFun), m_pObj(pObj)
{
}
virtual void Fire(const char* pURL, bool bOK)
{
if(m_pFun != NULL
&& m_pObj != NULL)
{
(m_pObj->*m_pFun)(pURL, bOK);
}
}
private:
Fun m_pFun;
O* m_pObj;
};
template<typename O, typename T>
CDownloadDelegate<O,T>* MakeDelegate(O* pObject, void (T::*pFun)(const char* pURL, bool))
{
return new CDownloadDelegate<O, T>(pObject, pFun);
}
class CDownloadEvent
{
public:
~CDownloadEvent()
{
vector<CDownloadDelegateBase*>::iterator itr = m_arDelegates.begin();
while (itr != m_arDelegates.end())
{
delete *itr;
++itr;
}
m_arDelegates.clear();
}
void operator += (CDownloadDelegateBase* p)
{
m_arDelegates.push_back(p);
}
void operator -= (CDownloadDelegateBase* p)
{
ITR itr = remove(m_arDelegates.begin(), m_arDelegates.end(), p);
ITR itrTemp = itr;
while (itrTemp != m_arDelegates.end())
{
delete *itr;
++itr;
}
m_arDelegates.erase(itr, m_arDelegates.end());
}
void operator()(const char* pURL, bool bOK)
{
ITR itrTemp = m_arDelegates.begin();
while (itrTemp != m_arDelegates.end())
{
(*itrTemp)->Fire(pURL, bOK);
++itrTemp;
}
}
private:
vector<CDownloadDelegateBase*> m_arDelegates;
typedef vector<CDownloadDelegateBase*>::iterator ITR;
};
class CMyDownloaderEx
{
public:
void DownloadFile(const char* pURL)
{
cout << "downloading: " << pURL << "" << endl;
downloadEvent(pURL, true);
}
CDownloadEvent downloadEvent;
};
class CMyFileEx
{
public:
void download()
{
CMyDownloaderEx downloader;
downloader.downloadEvent += MakeDelegate(this, &CMyFileEx::OnDownloadFinished);
downloader.DownloadFile("www.baidu.com");
}
virtual void OnDownloadFinished(const char* pURL, bool bOK)
{
cout << "OnDownloadFinished, URL:" << pURL << " status:" << bOK << endl;
}
};
可以使用下面的参数进行设置:
int _tmain(int argc, _TCHAR* argv[])
{
DownloadFile("www.baidu.com", OnDownloadFinished);
CMyFile f1;
f1.download();
CMyFileEx ff;
ff.download();
system("pause");
return 0;
}
总结:
Callback
的方式是面向过程,使用简单灵活;- Sink 的方式是面向对象的,在 C++ 中使用比较多,可以在一个 Sink 里封装一组回调接口,适用于一系列比较固定的回调事件;
- Delegate 的方法也是面向对象的,和 Sink 封装一组接口不同,Delegate 封装是以函数为单位,粒度比 Sink 更小更灵活;
参考
- 文中部分例子参考网络,因觉得挺好的故没有修改,也找不打最原始的出处,故没有给出链接,望见谅;