函数指针&回调&循环依赖

来源:互联网 发布:京都 知乎 编辑:程序博客网 时间:2024/05/16 00:49

说明

描述用函数指针、回调函数来去除循环依赖的例子。

参考资料

讨论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 oncetypedef 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 onceclass 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 = 11 + 1 = 2CMathLib::DoSomethingElse(), a = 2, b = 22 + 2 = 4CMathLib::DoSomethingElse(), a = 3, b = 33 + 3 = 6CMathLib::DoSomethingElse(), a = 4, b = 44 + 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的名字取得不好,其实应该取个被动含义的词语。为了省事,代码暂维持不变。

对话框效果

Function Pointer

对话框资源

IDD_FUNCPOINTERMFC_DIALOG DIALOGEX 0, 0, 221, 118STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENUEXSTYLE WS_EX_APPWINDOWCAPTION "FuncPointerMfc"FONT 8, "MS Shell Dlg", 0, 0, 0x1BEGIN    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_AUTOHSCROLLEND

对话框的.h,.cpp

//.hpublic:    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;//.cppstatic 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 handlersBOOL 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 oncetypedef 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 onceclass 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 onceclass 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 onceclass 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 onceclass 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 onceclass 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 dialogclass CFuncPointerMfcDlg : public CDialogEx, public CMsgObserver{// Constructionpublic:    CFuncPointerMfcDlg(CWnd* pParent = NULL);   // standard constructor// Dialog Data    enum { IDD = IDD_FUNCPOINTERMFC_DIALOG };    protected:    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support// Implementationprotected:    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 handlersBOOL 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);}
0 0
原创粉丝点击