说明
描述用函数指针、回调函数来去除循环依赖的例子。
参考资料
讨论C和C++中函数指针的一个pdf文件:
http://www.newty.de/fpt/zip/e_fpt.pdf
示例
这里是C++的一个例子。
要点
类说明:
- CMathLib:执行某些算法(这里以加法运算为例),被其他类或函数调用;也可以理解为底层支持库。在示例中,要将这个类中的一个静态成员函数作为指针,传给其他的类(CCaller)
- CCaller:相当于对CMathLib的一层wrapper,用户(User)把CMathLib的算法函数作为函数指针,复制给CCaller。CCaller在执行具体的事务的时候,调用这个函数指针(相当于回调函数)真正去do something。
- main():相当于User。
User视角
#include "MathLib.h"
#include "Caller.h"
int _tmain(int argc, _TCHAR* argv[])
{
CCaller caller(&CMathLib::Add);
caller.DoSomething();
return 0;
}
Caller.h,.cpp
头文件:
#pragma once
typedef int (*AddFunc)(int a, int b);
class CCaller
{
public:
explicit CCaller(AddFunc f);
~CCaller(void);
void DoSomething();
private:
AddFunc m_addFunc;
};
实现文件:
#include "StdAfx.h"
#include "Caller.h"
#include <assert.h>
CCaller::CCaller(AddFunc f): m_addFunc(f)
{
}
CCaller::~CCaller(void)
{
}
void CCaller::DoSomething()
{
assert(m_addFunc != NULL);
for (int i = 1; i < 5; i++) {
printf("%d + %d = %d\n", i, i, m_addFunc(i, i));
}
}
CMathLib.h,.cpp
头文件:
#pragma once
class CMathLib
{
public:
CMathLib(void);
~CMathLib(void);
static int Add(int a, int b);
private:
void DoSomthingElse(int a, int b);
};
实现文件
#include "StdAfx.h"
#include "MathLib.h"
CMathLib* m_lib;
CMathLib::CMathLib(void)
{
m_lib = this;
}
CMathLib::~CMathLib(void)
{
}
void CMathLib::DoSomthingElse(int a, int b)
{
printf("CMathLib::DoSomethingElse(), a = %d, b = %d\n", a, b);
}
int CMathLib::Add(int a, int b)
{
m_lib->DoSomthingElse(a, b);
return a + b;
}
这里特别让静态成员函数范围了类的非静态函数。——参见后面的背景说明。
运行结果
CMathLib::DoSomethingElse(), a = 1, b = 1
1 + 1 = 2
CMathLib::DoSomethingElse(), a = 2, b = 2
2 + 2 = 4
CMathLib::DoSomethingElse(), a = 3, b = 3
3 + 3 = 6
CMathLib::DoSomethingElse(), a = 4, b = 4
4 + 4 = 8
请按任意键继续. . .
示例背景说明
假定有一个对话框应用程序,其中对话框类是A;另外有一些算法代码,比如class B。A需要调用B的算法(成员函数),而B在算法处理时,一些异常需要通过UI反馈给User。
方法X
对话框A定义一个非静态成员函数,如Notify(const char* msg); 这个函数通过DDX等机制把msg更新到用户界面上。
算法B调用A的这个Notify(),把算法处理中的异常通过A反馈给用户。
在这个过程中,A要调用算法B,所以A会依赖B;B需要通过A把消息反馈给UI,所以B依赖于A。如此,形成了循环依赖。
当然,即便循环依赖,编译&链接都是没有问题,功能也是没有问题。
不过我们不喜欢循环依赖,所以要考虑另一种方法。
方法Y
其中一种方法就是增加一个中间层,让对话框和算法类都依赖于这个中间层,姑且称为这个中间层为class C。从职责驱动的角度来分析:
- 算法B要输出异常消息的时候,调用C,由C去完成显示消息的职责;
- 对话框A只是调用算法B,去完成某种算法。
- 而真正的显示消息,只能是A。所以需要把A和C关联起来,且根据前面讨论的不希望出现循环依赖的情况下,是要A调用C,而不是C调用A。
- 其中一直方法,就是A把自己的显示消息的一个接口告诉C;C需要显示消息的时候,直接回调A定义的这个接口即可。至于A真正显示消息的处理流程,C不希望关心。
- 所以,C是一个很轻量级的中间层,A和B都依赖C;C显然不依赖于B;C看起来不依赖A,至少从形式上如此。这个形式化解耦或去除循环依赖就是通过回调。
当然,A也可以把回调接口告诉算法B,似乎可以简化上面的流程。不过,在未来A调用了新的算法D、算法E等等的时候,会发现单独另一个中间层C是较好的方式。
下面是部分代码。
以下X和Y中Notifier的名字取得不好,其实应该取个被动含义的词语。为了省事,代码暂维持不变。
对话框效果
对话框资源
IDD_FUNCPOINTERMFC_DIALOG DIALOGEX 0, 0, 221, 118
STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
EXSTYLE WS_EX_APPWINDOW
CAPTION "FuncPointerMfc"
FONT 8, "MS Shell Dlg", 0, 0, 0x1
BEGIN
LTEXT "Calulate: a / b",IDC_STATIC,32,15,139,10
LTEXT "a:",IDC_STATIC,46,32,12,12,SS_CENTERIMAGE
EDITTEXT IDC_A,64,32,51,12,ES_AUTOHSCROLL
LTEXT "b:",IDC_STATIC,131,32,33,12,SS_CENTERIMAGE
EDITTEXT IDC_B,143,32,51,12,ES_AUTOHSCROLL
LTEXT "Result:",IDC_STATIC,31,53,28,12,SS_CENTERIMAGE
EDITTEXT IDC_RESULT,66,53,51,12,ES_AUTOHSCROLL
PUSHBUTTON "Calculate",IDC_CALCULATE,64,69,95,18
EDITTEXT IDC_MSG,25,91,182,20,ES_AUTOHSCROLL
END
对话框的.h,.cpp
//.h
public:
afx_msg void OnBnClickedCalculate();
void InnerNotify(const char *msg);
static void Notify(const char *msg);
private:
int m_a;
int m_b;
int m_result;
CString m_msg;
CMathLib m_mathLib;
//.cpp
static CFuncPointerMfcDlg *m_dlg = NULL;
CFuncPointerMfcDlg::CFuncPointerMfcDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CFuncPointerMfcDlg::IDD, pParent)
, m_a(0)
, m_b(0)
, m_result(0)
, m_msg(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CFuncPointerMfcDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_A, m_a);
DDX_Text(pDX, IDC_B, m_b);
DDX_Text(pDX, IDC_RESULT, m_result);
DDX_Text(pDX, IDC_MSG, m_msg);
}
BEGIN_MESSAGE_MAP(CFuncPointerMfcDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_CALCULATE, &CFuncPointerMfcDlg::OnBnClickedCalculate)
END_MESSAGE_MAP()
// CFuncPointerMfcDlg message handlers
BOOL CFuncPointerMfcDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// ...........
// TODO: Add extra initialization here
m_dlg = this;
m_mathLib.SetNotifier(&CFuncPointerMfcDlg::Notify);
return TRUE; // return TRUE unless you set the focus to a control
}
void CFuncPointerMfcDlg::InnerNotify(const char *msg)
{
m_msg = msg;
UpdateData(FALSE);
}
void CFuncPointerMfcDlg::Notify(const char *msg)
{
m_dlg->InnerNotify(msg);
}
void CFuncPointerMfcDlg::OnBnClickedCalculate()
{
UpdateData(TRUE);
m_result = m_mathLib.Mod(m_a, m_b);
UpdateData(FALSE);
}
MathLib.h
#pragma once
typedef void (*Notify)(const char *msg);
class CMathLib
{
public:
CMathLib(void);
~CMathLib(void);
int Mod(int a, int b);
void SetNotifier(Notify notify);
private:
Notify m_notify;
};
MathLib.cpp
#include "StdAfx.h"
#include "MathLib.h"
static void NullNotify(const char *msg)
{
// do nothing
}
CMathLib::CMathLib(void): m_notify(NullNotify)
{
}
CMathLib::~CMathLib(void)
{
}
int CMathLib::Mod(int a, int b)
{
const int DEFAULT_RESULT = 0;
if (0 == b) {
m_notify("Oooh, b is zero!");
return DEFAULT_RESULT;
}
return a / b;
}
void CMathLib::SetNotifier(Notify notify)
{
m_notify = notify;
}
方法Z
直接上代码。对话框资源一样,把Nofity接口化处理。
CNotifierInterface
头文件:
#pragma once
class CNotifierInterface
{
public:
CNotifierInterface(void);
virtual ~CNotifierInterface(void);
virtual void Notify(const char *format, ...) = 0;
};
实现文件:
#include "StdAfx.h"
#include "NotifierInterface.h"
CNotifierInterface::CNotifierInterface(void)
{
}
CNotifierInterface::~CNotifierInterface(void)
{
}
CNullNotifier
头文件:
#pragma once
#include "NotifierInterface.h"
class CNullNotifier: public CNotifierInterface
{
public:
CNullNotifier(void);
virtual ~CNullNotifier(void);
void Notify(const char *format, ...);
};
实现文件:
#include "StdAfx.h"
#include "NullNotifier.h"
CNullNotifier::CNullNotifier(void)
{
}
CNullNotifier::~CNullNotifier(void)
{
}
void CNullNotifier::Notify(const char *format, ...)
{
// do nothing
}
CMathLib
头文件:
#pragma once
class CNotifierInterface;
class CMathLib
{
public:
CMathLib(void);
~CMathLib(void);
int Mod(int a, int b);
void SetNotifier(CNotifierInterface *pNotifier);
private:
CNotifierInterface *m_pNotifier;
};
实现文件:
#include "StdAfx.h"
#include "MathLib.h"
#include "NotifierInterface.h"
#include "NullNotifier.h"
static CNullNotifier m_nullNotifier;
CMathLib::CMathLib(void): m_pNotifier(&m_nullNotifier)
{
}
CMathLib::~CMathLib(void)
{
}
int CMathLib::Mod(int a, int b)
{
const int DEFAULT_RESULT = 0;
if (0 == b) {
m_pNotifier->Notify("Oooh, b is zero!");
return DEFAULT_RESULT;
}
return a / b;
}
void CMathLib::SetNotifier(CNotifierInterface *pNotifier)
{
if (m_pNotifier) m_pNotifier = pNotifier;
}
对话框类
仅提供部分代码,省略掉MFC框架自动生成的代码,以及上一节已经给出的消息映射等代码。
头文件:
#pragma once
#include "MathLib.h"
#include "NotifierInterface.h"
class CFuncPointerMfcDlg : public CDialogEx, public CNotifierInterface
{
//..............
public:
afx_msg void OnBnClickedCalculate();
void Notify(const char *format, ...);
private:
int m_a;
int m_b;
int m_result;
CString m_msg;
CMathLib m_mathLib;
};
实现文件:
#include "stdafx.h"
#include "FuncPointerMfc.h"
#include "FuncPointerMfcDlg.h"
#include "afxdialogex.h"
BOOL CFuncPointerMfcDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// ..............
// TODO: Add extra initialization here
m_mathLib.SetNotifier(this);
return TRUE; // return TRUE unless you set the focus to a control
}
void CFuncPointerMfcDlg::Notify(const char *format, ...)
{
m_msg = format; //此处简化处理。。。
UpdateData(FALSE);
}
void CFuncPointerMfcDlg::OnBnClickedCalculate()
{
UpdateData(TRUE);
m_msg.Empty();
m_result = m_mathLib.Mod(m_a, m_b);
UpdateData(FALSE);
}
functor
如果愿意,在方法Z中,CMathLib中的如下调用可以改为functor:
m_pNotifier->Notify("Oooh, b is zero!");
方法就是把所有的Notify()函数改成operator():
//void Notify(const char *format, ...);
void operator()(const char *format, ...);
然后在调用的时候如下:
(*m_pNotifier)("Oooh, b is zero!");
如此,代码看起来会简洁一些。
方法Z+
再改为Observer模式。之前已有的Notifer相关的class均移除掉。完整的代码如下:
CStringWrapper
这个class参考:可变长参数&日期等
头文件:
#pragma once
class CStringWrapper
{
public:
static CString Wrap(const char *format, ...);
};
实现文件:
#include "StdAfx.h"
#include "StringWrapper.h"
CString CStringWrapper::Wrap(const char *format, ...)
{
const size_t BUFFER_MAX_SIZE = 1024;
static TCHAR buffer[BUFFER_MAX_SIZE];
va_list ap;
va_start(ap, format);
vsprintf(buffer, format, ap);
va_end (ap);
return CString(buffer);
}
CMsgSubject
头文件:
#pragma once
class CMsgObserver;
class CMsgSubject
{
public:
CMsgSubject(void);
virtual ~CMsgSubject(void);
void AssignObserver(CMsgObserver *observer);
virtual void Notify(const CString &msg);
private:
CMsgObserver *m_pObserver;
};
实现文件:
#include "StdAfx.h"
#include "MsgSubject.h"
#include "NullMsgObserver.h"
// Avoid to use new().
static CNullMsgObserver m_nullMsgObserver;
CMsgSubject::CMsgSubject(void) : m_pObserver(&m_nullMsgObserver)
{
}
CMsgSubject::~CMsgSubject(void)
{
}
void CMsgSubject::AssignObserver(CMsgObserver *observer)
{
if (observer != NULL) m_pObserver = observer;
}
void CMsgSubject::Notify(const CString &msg)
{
m_pObserver->Update(msg);
}
CMsgObserver
头文件:
#pragma once
class CMsgObserver
{
public:
CMsgObserver(void);
virtual ~CMsgObserver(void);
virtual void Update(const CString &msg) = 0;
};
实现文件:
#include "StdAfx.h"
#include "MsgObserver.h"
CMsgObserver::CMsgObserver(void)
{
}
CMsgObserver::~CMsgObserver(void)
{
}
CMathLib
由于抽象出了一个CMsgSubject,所以现在的CMathLib的职责就简单多了。
头文件:
#pragma once
#include "MsgSubject.h"
class CMathLib: public CMsgSubject
{
public:
CMathLib(void);
~CMathLib(void);
int Mod(int a, int b);
};
实现文件:
#include "StdAfx.h"
#include "MathLib.h"
#include "StringWrapper.h"
CMathLib::CMathLib(void)
{
}
CMathLib::~CMathLib(void)
{
}
int CMathLib::Mod(int a, int b)
{
const int DEFAULT_RESULT = 0;
if (0 == b) {
Notify("Oooh, b is zero!");
return DEFAULT_RESULT;
}
CString msg(CStringWrapper::Wrap("%d / %d = %d", a, b, a / b));
Notify(msg);
return a / b;
}
CNullMsgObserver
头文件:
#pragma once
#include "MsgObserver.h"
class CNullMsgObserver : public CMsgObserver
{
public:
CNullMsgObserver(void);
virtual ~CNullMsgObserver(void);
void Update(const CString &msg);
};
实现文件:
#include "StdAfx.h"
#include "NullMsgObserver.h"
CNullMsgObserver::CNullMsgObserver(void)
{
}
CNullMsgObserver::~CNullMsgObserver(void)
{
}
void CNullMsgObserver::Update(const CString &msg)
{
// do nothing
}
对话框类
同样,这里的实现文件会删除无关紧要的代码。
头文件:
// FuncPointerMfcDlg.h : header file
//
#pragma once
#include "MathLib.h"
#include "MsgObserver.h"
// CFuncPointerMfcDlg dialog
class CFuncPointerMfcDlg : public CDialogEx, public CMsgObserver
{
// Construction
public:
CFuncPointerMfcDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
enum { IDD = IDD_FUNCPOINTERMFC_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
// Implementation
protected:
HICON m_hIcon;
// Generated message map functions
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedCalculate();
void Update(const CString &msg);
private:
int m_a;
int m_b;
int m_result;
CString m_msg;
CMathLib m_mathLib;
};
实现文件:
// FuncPointerMfcDlg.cpp : implementation file
//
CFuncPointerMfcDlg::CFuncPointerMfcDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CFuncPointerMfcDlg::IDD, pParent)
, m_a(0)
, m_b(0)
, m_result(0)
, m_msg(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CFuncPointerMfcDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Text(pDX, IDC_A, m_a);
DDX_Text(pDX, IDC_B, m_b);
DDX_Text(pDX, IDC_RESULT, m_result);
DDX_Text(pDX, IDC_MSG, m_msg);
}
BEGIN_MESSAGE_MAP(CFuncPointerMfcDlg, CDialogEx)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_CALCULATE, &CFuncPointerMfcDlg::OnBnClickedCalculate)
END_MESSAGE_MAP()
// CFuncPointerMfcDlg message handlers
BOOL CFuncPointerMfcDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// .........
// TODO: Add extra initialization here
m_mathLib.AssignObserver(this);
return TRUE; // return TRUE unless you set the focus to a control
}
void CFuncPointerMfcDlg::Update(const CString &msg)
{
m_msg = msg;
UpdateData(FALSE);
}
void CFuncPointerMfcDlg::OnBnClickedCalculate()
{
UpdateData(TRUE);
m_msg.Empty();
m_result = m_mathLib.Mod(m_a, m_b);
UpdateData(FALSE);
}